From 7d6ea123e08b793a87f35290e740cbef547c3862 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Thu, 25 Jan 2024 12:50:59 +0530 Subject: [PATCH 01/37] feat: update proxy data type for response handler input --- .../networkhandler/genericNetworkHandler.js | 7 ++- src/controllers/delivery.ts | 20 +++---- src/interfaces/DestinationService.ts | 6 +- src/services/comparator.ts | 6 +- src/services/destination/cdkV1Integration.ts | 6 +- src/services/destination/cdkV2Integration.ts | 6 +- src/services/destination/nativeIntegration.ts | 38 ++++++------ .../destination/postTransformation.ts | 12 ++-- src/types/index.ts | 58 +++++++++++++------ .../adobe_analytics/networkHandler.js | 8 ++- src/v0/destinations/braze/networkHandler.js | 3 +- .../campaign_manager/networkHandler.js | 3 +- .../destinations/clevertap/networkHandler.js | 3 +- .../criteo_audience/networkHandler.js | 3 +- src/v0/destinations/fb/networkHandler.js | 3 +- src/v0/destinations/ga4/networkHandler.js | 7 ++- .../networkHandler.js | 3 +- .../networkHandler.js | 3 +- .../networkHandler.js | 3 +- .../destinations/intercom/networkHandler.js | 5 +- src/v0/destinations/marketo/networkHandler.js | 5 +- .../marketo_static_list/networkHandler.js | 5 +- src/v0/destinations/pardot/networkHandler.js | 3 +- src/v0/destinations/reddit/networkHandler.js | 3 +- .../destinations/salesforce/networkHandler.js | 5 +- .../salesforce_oauth/networkHandler.js | 5 +- .../networkHandler.js | 3 +- .../the_trade_desk/networkHandler.js | 3 +- .../destinations/tiktok_ads/networkHandler.js | 3 +- src/v0/util/facebookUtils/networkHandler.js | 3 +- .../campaign_manager/networkHandler.js | 5 +- 31 files changed, 146 insertions(+), 100 deletions(-) diff --git a/src/adapters/networkhandler/genericNetworkHandler.js b/src/adapters/networkhandler/genericNetworkHandler.js index bcbcb21259..d9358085f4 100644 --- a/src/adapters/networkhandler/genericNetworkHandler.js +++ b/src/adapters/networkhandler/genericNetworkHandler.js @@ -17,13 +17,14 @@ const tags = require('../../v0/util/tags'); * will act as fall-fack for such scenarios. * */ -const responseHandler = (destinationResponse, dest) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType } = responseParams; const { status } = destinationResponse; - const message = `[Generic Response Handler] Request for destination: ${dest} Processed Successfully`; + const message = `[Generic Response Handler] Request for destination: ${destType} Processed Successfully`; // if the response from destination is not a success case build an explicit error if (!isHttpStatusSuccess(status)) { throw new NetworkError( - `[Generic Response Handler] Request failed for destination ${dest} with status: ${status}`, + `[Generic Response Handler] Request failed for destination ${destType} with status: ${status}`, status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), diff --git a/src/controllers/delivery.ts b/src/controllers/delivery.ts index eba24ccf58..e0839a7eda 100644 --- a/src/controllers/delivery.ts +++ b/src/controllers/delivery.ts @@ -3,11 +3,11 @@ import { Context } from 'koa'; import { MiscService } from '../services/misc'; import { - DeliveriesResponse, - DeliveryResponse, + DeliveryV1Response, + DeliveryV0Response, ProcessorTransformationOutput, - ProxyDeliveriesRequest, - ProxyDeliveryRequest, + ProxyV0Request, + ProxyV1Request, } from '../types/index'; import { ServiceSelector } from '../helpers/serviceSelector'; import { DeliveryTestService } from '../services/delivertTest/deliveryTest'; @@ -22,9 +22,9 @@ const NON_DETERMINABLE = 'Non-determinable'; export class DeliveryController { public static async deliverToDestination(ctx: Context) { logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body)); - let deliveryResponse: DeliveryResponse; + let deliveryResponse: DeliveryV0Response; const requestMetadata = MiscService.getRequestMetadata(ctx); - const deliveryRequest = ctx.request.body as ProxyDeliveryRequest; + const deliveryRequest = ctx.request.body as ProxyV0Request; const { destination }: { destination: string } = ctx.params; const integrationService = ServiceSelector.getNativeDestinationService(); try { @@ -33,7 +33,7 @@ export class DeliveryController { destination, requestMetadata, 'v0', - )) as DeliveryResponse; + )) as DeliveryV0Response; } catch (error: any) { const { metadata } = deliveryRequest; const metaTO = integrationService.getTags( @@ -57,9 +57,9 @@ export class DeliveryController { public static async deliverToDestinationV1(ctx: Context) { logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body)); - let deliveryResponse: DeliveriesResponse; + let deliveryResponse: DeliveryV1Response; const requestMetadata = MiscService.getRequestMetadata(ctx); - const deliveryRequest = ctx.request.body as ProxyDeliveriesRequest; + const deliveryRequest = ctx.request.body as ProxyV1Request; const { destination }: { destination: string } = ctx.params; const integrationService = ServiceSelector.getNativeDestinationService(); try { @@ -68,7 +68,7 @@ export class DeliveryController { destination, requestMetadata, 'v1', - )) as DeliveriesResponse; + )) as DeliveryV1Response; } catch (error: any) { const { metadata } = deliveryRequest; const metaTO = integrationService.getTags( diff --git a/src/interfaces/DestinationService.ts b/src/interfaces/DestinationService.ts index bf39024d85..4947089b5d 100644 --- a/src/interfaces/DestinationService.ts +++ b/src/interfaces/DestinationService.ts @@ -1,5 +1,5 @@ import { - DeliveryResponse, + DeliveryV0Response, MetaTransferObject, ProcessorTransformationRequest, ProcessorTransformationResponse, @@ -8,7 +8,7 @@ import { UserDeletionRequest, UserDeletionResponse, ProxyRequest, - DeliveriesResponse, + DeliveryV1Response, } from '../types/index'; export interface DestinationService { @@ -49,7 +49,7 @@ export interface DestinationService { destinationType: string, requestMetadata: NonNullable, version: string, - ): Promise; + ): Promise; processUserDeletion( requests: UserDeletionRequest[], diff --git a/src/services/comparator.ts b/src/services/comparator.ts index d1e085b4bd..36cb0ebd5a 100644 --- a/src/services/comparator.ts +++ b/src/services/comparator.ts @@ -1,8 +1,8 @@ /* eslint-disable class-methods-use-this */ import { DestinationService } from '../interfaces/DestinationService'; import { - DeliveriesResponse, - DeliveryResponse, + DeliveryV0Response, + DeliveryV1Response, Destination, ErrorDetailer, MetaTransferObject, @@ -370,7 +370,7 @@ export class ComparatorService implements DestinationService { destinationType: string, requestMetadata: NonNullable, version: string, - ): Promise { + ): Promise { const primaryResplist = await this.primaryService.deliver( event, destinationType, diff --git a/src/services/destination/cdkV1Integration.ts b/src/services/destination/cdkV1Integration.ts index 197e3162ea..c6e60f5857 100644 --- a/src/services/destination/cdkV1Integration.ts +++ b/src/services/destination/cdkV1Integration.ts @@ -4,7 +4,7 @@ import path from 'path'; import { TransformationError } from '@rudderstack/integrations-lib'; import { DestinationService } from '../../interfaces/DestinationService'; import { - DeliveryResponse, + DeliveryV0Response, ErrorDetailer, MetaTransferObject, ProcessorTransformationRequest, @@ -14,7 +14,7 @@ import { UserDeletionRequest, UserDeletionResponse, ProxyRequest, - DeliveriesResponse, + DeliveryV1Response, } from '../../types/index'; import { DestinationPostTransformationService } from './postTransformation'; import tags from '../../v0/util/tags'; @@ -121,7 +121,7 @@ export class CDKV1DestinationService implements DestinationService { _event: ProxyRequest, _destinationType: string, _requestMetadata: NonNullable, - ): Promise { + ): Promise { throw new TransformationError('CDV1 Does not Implement Delivery Routine'); } diff --git a/src/services/destination/cdkV2Integration.ts b/src/services/destination/cdkV2Integration.ts index be7f0e51d5..c18a5cd936 100644 --- a/src/services/destination/cdkV2Integration.ts +++ b/src/services/destination/cdkV2Integration.ts @@ -5,7 +5,7 @@ import { TransformationError } from '@rudderstack/integrations-lib'; import { processCdkV2Workflow } from '../../cdk/v2/handler'; import { DestinationService } from '../../interfaces/DestinationService'; import { - DeliveryResponse, + DeliveryV0Response, ErrorDetailer, MetaTransferObject, ProcessorTransformationRequest, @@ -16,7 +16,7 @@ import { UserDeletionRequest, UserDeletionResponse, ProxyRequest, - DeliveriesResponse, + DeliveryV1Response, } from '../../types/index'; import tags from '../../v0/util/tags'; import { DestinationPostTransformationService } from './postTransformation'; @@ -170,7 +170,7 @@ export class CDKV2DestinationService implements DestinationService { _event: ProxyRequest, _destinationType: string, _requestMetadata: NonNullable, - ): Promise { + ): Promise { throw new TransformationError('CDKV2 Does not Implement Delivery Routine'); } diff --git a/src/services/destination/nativeIntegration.ts b/src/services/destination/nativeIntegration.ts index 6b680e3f4a..2dd78b58e2 100644 --- a/src/services/destination/nativeIntegration.ts +++ b/src/services/destination/nativeIntegration.ts @@ -5,7 +5,7 @@ import groupBy from 'lodash/groupBy'; import cloneDeep from 'lodash/cloneDeep'; import { DestinationService } from '../../interfaces/DestinationService'; import { - DeliveryResponse, + DeliveryV0Response, ErrorDetailer, MetaTransferObject, ProcessorTransformationRequest, @@ -16,9 +16,9 @@ import { UserDeletionRequest, UserDeletionResponse, ProxyRequest, - ProxyDeliveriesRequest, - ProxyDeliveryRequest, - DeliveriesResponse, + ProxyV0Request, + ProxyV1Request, + DeliveryV1Response, DeliveryJobState, } from '../../types/index'; import { DestinationPostTransformationService } from './postTransformation'; @@ -181,7 +181,7 @@ export class NativeIntegrationDestinationService implements DestinationService { destinationType: string, _requestMetadata: NonNullable, version: string, - ): Promise { + ): Promise { try { const { networkHandler, handlerVersion } = networkHandlerFactory.getNetworkHandler( destinationType, @@ -191,24 +191,22 @@ export class NativeIntegrationDestinationService implements DestinationService { const processedProxyResponse = networkHandler.processAxiosResponse(rawProxyResponse); let rudderJobMetadata = version.toLowerCase() === 'v1' - ? (deliveryRequest as ProxyDeliveriesRequest).metadata - : (deliveryRequest as ProxyDeliveryRequest).metadata; + ? (deliveryRequest as ProxyV1Request).metadata + : (deliveryRequest as ProxyV0Request).metadata; if (version.toLowerCase() === 'v1' && handlerVersion.toLowerCase() === 'v0') { rudderJobMetadata = rudderJobMetadata[0]; } - - let responseProxy = networkHandler.responseHandler( - { - ...processedProxyResponse, - rudderJobMetadata, - }, - destinationType, - ); + const responseParams = { + destinationResponse: processedProxyResponse, + rudderJobMetadata, + destType: destinationType, + }; + let responseProxy = networkHandler.responseHandler(responseParams); // Adaption Logic for V0 to V1 if (handlerVersion.toLowerCase() === 'v0' && version.toLowerCase() === 'v1') { - const v0Response = responseProxy as DeliveryResponse; - const jobStates = (deliveryRequest as ProxyDeliveriesRequest).metadata.map( + const v0Response = responseProxy as DeliveryV0Response; + const jobStates = (deliveryRequest as ProxyV1Request).metadata.map( (metadata) => ({ error: JSON.stringify(v0Response.destinationResponse?.response), @@ -221,7 +219,7 @@ export class NativeIntegrationDestinationService implements DestinationService { status: v0Response.status, message: v0Response.message, authErrorCategory: v0Response.authErrorCategory, - } as DeliveriesResponse; + } as DeliveryV1Response; } return responseProxy; } catch (err: any) { @@ -236,10 +234,10 @@ export class NativeIntegrationDestinationService implements DestinationService { ); if (version.toLowerCase() === 'v1') { - metaTO.metadatas = (deliveryRequest as ProxyDeliveriesRequest).metadata; + metaTO.metadatas = (deliveryRequest as ProxyV1Request).metadata; return DestinationPostTransformationService.handlevV1DeliveriesFailureEvents(err, metaTO); } - metaTO.metadata = (deliveryRequest as ProxyDeliveryRequest).metadata; + metaTO.metadata = (deliveryRequest as ProxyV0Request).metadata; return DestinationPostTransformationService.handleDeliveryFailureEvents(err, metaTO); } } diff --git a/src/services/destination/postTransformation.ts b/src/services/destination/postTransformation.ts index eef4152b2b..081c40a07c 100644 --- a/src/services/destination/postTransformation.ts +++ b/src/services/destination/postTransformation.ts @@ -8,10 +8,10 @@ import { ProcessorTransformationResponse, RouterTransformationResponse, ProcessorTransformationOutput, - DeliveryResponse, + DeliveryV0Response, MetaTransferObject, UserDeletionResponse, - DeliveriesResponse, + DeliveryV1Response, DeliveryJobState, } from '../../types/index'; import { generateErrorObject } from '../../v0/util'; @@ -145,7 +145,7 @@ export class DestinationPostTransformationService { public static handleDeliveryFailureEvents( error: any, metaTo: MetaTransferObject, - ): DeliveryResponse { + ): DeliveryV0Response { const errObj = generateErrorObject(error, metaTo.errorDetails, false); const resp = { status: errObj.status, @@ -155,7 +155,7 @@ export class DestinationPostTransformationService { ...(errObj.authErrorCategory && { authErrorCategory: errObj.authErrorCategory, }), - } as DeliveryResponse; + } as DeliveryV0Response; ErrorReportingService.reportError(error, metaTo.errorContext, resp); return resp; @@ -164,7 +164,7 @@ export class DestinationPostTransformationService { public static handlevV1DeliveriesFailureEvents( error: FixMe, metaTo: MetaTransferObject, - ): DeliveriesResponse { + ): DeliveryV1Response { const errObj = generateErrorObject(error, metaTo.errorDetails, false); const metadataArray = metaTo.metadatas; if (!Array.isArray(metadataArray)) { @@ -189,7 +189,7 @@ export class DestinationPostTransformationService { authErrorCategory: errObj.authErrorCategory, message: errObj.message.toString(), status: errObj.status, - } as DeliveriesResponse; + } as DeliveryV1Response; ErrorReportingService.reportError(error, metaTo.errorContext, resp); return resp; diff --git a/src/types/index.ts b/src/types/index.ts index f4432e5c2a..df8d3a9182 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -18,7 +18,7 @@ type ProcessorTransformationOutput = { files?: Record; }; -type ProxyDeliveryRequest = { +type ProxyV0Request = { version: string; type: string; method: string; @@ -33,10 +33,10 @@ type ProxyDeliveryRequest = { FORM?: Record; }; files?: Record; - metadata: Metadata; + metadata: ProxyMetdata; }; -type ProxyDeliveriesRequest = { +type ProxyV1Request = { version: string; type: string; method: string; @@ -51,10 +51,24 @@ type ProxyDeliveriesRequest = { FORM?: Record; }; files?: Record; - metadata: Metadata[]; + metadata: ProxyMetdata[]; + destinationConfig: Record; }; -type ProxyRequest = ProxyDeliveryRequest | ProxyDeliveriesRequest; +type ProxyRequest = ProxyV0Request | ProxyV1Request; + +type ProxyMetdata = { + jobId: number; + attemptNum: number; + userId: string; + sourceId: string; + destinationId: string; + workspaceId: string; + secret: Record; + destInfo?: Record; + omitempty?: Record; + dontBatch: boolean; +}; type Metadata = { sourceId: string; @@ -172,7 +186,7 @@ type SourceTransformationResponse = { statTags: object; }; -type DeliveryResponse = { +type DeliveryV0Response = { status: number; message: string; destinationResponse: any; @@ -183,12 +197,12 @@ type DeliveryResponse = { type DeliveryJobState = { error: string; statusCode: number; - metadata: Metadata; + metadata: ProxyMetdata; }; -type DeliveriesResponse = { - status?: number; - message?: string; +type DeliveryV1Response = { + status: number; + message: string; statTags?: object; authErrorCategory?: string; response: DeliveryJobState[]; @@ -236,13 +250,22 @@ type ErrorDetailer = { sourceId?: string; }; -type MetaTransferObject = { - metadatas?: Metadata[]; - metadata?: Metadata; +type MetaTransferObjectForProxy = { + metadata?: ProxyMetdata; + metadatas?: ProxyMetdata[]; errorDetails: ErrorDetailer; errorContext: string; }; +type MetaTransferObject = + | { + metadatas?: Metadata[]; + metadata?: Metadata; + errorDetails: ErrorDetailer; + errorContext: string; + } + | MetaTransferObjectForProxy; + type UserTransformationResponse = { transformedEvent: RudderMessage; metadata: Metadata; @@ -307,8 +330,8 @@ type SourceInput = { export { ComparatorInput, DeliveryJobState, - DeliveryResponse, - DeliveriesResponse, + DeliveryV0Response, + DeliveryV1Response, Destination, ErrorDetailer, MessageIdMetadataMap, @@ -317,9 +340,10 @@ export { ProcessorTransformationOutput, ProcessorTransformationRequest, ProcessorTransformationResponse, - ProxyDeliveriesRequest, - ProxyDeliveryRequest, + ProxyMetdata, ProxyRequest, + ProxyV0Request, + ProxyV1Request, RouterTransformationRequest, RouterTransformationRequestData, RouterTransformationResponse, diff --git a/src/v0/destinations/adobe_analytics/networkHandler.js b/src/v0/destinations/adobe_analytics/networkHandler.js index 0ec1fad286..8715721f85 100644 --- a/src/v0/destinations/adobe_analytics/networkHandler.js +++ b/src/v0/destinations/adobe_analytics/networkHandler.js @@ -15,7 +15,9 @@ function extractContent(xmlPayload, tagName) { return match ? match[1] : null; } -const responseHandler = (destinationResponse, dest) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType } = responseParams; + const message = `[${DESTINATION}] - Request Processed Successfully`; const { response, status } = destinationResponse; @@ -27,11 +29,11 @@ const responseHandler = (destinationResponse, dest) => { if (responseStatus === 'FAILURE') { if (reason) { throw new InstrumentationError( - `[${DESTINATION} Response Handler] Request failed for destination ${dest} : ${reason}`, + `[${DESTINATION} Response Handler] Request failed for destination ${destType} : ${reason}`, ); } else { throw new InstrumentationError( - `[${DESTINATION} Response Handler] Request failed for destination ${dest} with a general error`, + `[${DESTINATION} Response Handler] Request failed for destination ${destType} with a general error`, ); } } diff --git a/src/v0/destinations/braze/networkHandler.js b/src/v0/destinations/braze/networkHandler.js index c6cf7222ea..b1363419b3 100644 --- a/src/v0/destinations/braze/networkHandler.js +++ b/src/v0/destinations/braze/networkHandler.js @@ -11,7 +11,8 @@ const tags = require('../../util/tags'); const stats = require('../../../util/stats'); // eslint-disable-next-line @typescript-eslint/no-unused-vars -const responseHandler = (destinationResponse, _dest) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request for ${DESTINATION} Processed Successfully`; const { response, status } = destinationResponse; // if the response from destination is not a success case build an explicit error diff --git a/src/v0/destinations/campaign_manager/networkHandler.js b/src/v0/destinations/campaign_manager/networkHandler.js index a1fa24835c..df13b72adc 100644 --- a/src/v0/destinations/campaign_manager/networkHandler.js +++ b/src/v0/destinations/campaign_manager/networkHandler.js @@ -44,7 +44,8 @@ function checkIfFailuresAreRetryable(response) { } } -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `[CAMPAIGN_MANAGER Response Handler] - Request Processed Successfully`; const { response, status } = destinationResponse; diff --git a/src/v0/destinations/clevertap/networkHandler.js b/src/v0/destinations/clevertap/networkHandler.js index e17afb57d1..02b523f3fc 100644 --- a/src/v0/destinations/clevertap/networkHandler.js +++ b/src/v0/destinations/clevertap/networkHandler.js @@ -7,7 +7,8 @@ const { } = require('../../../adapters/utils/networkUtils'); const tags = require('../../util/tags'); -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = 'Request Processed Successfully'; const { response, status } = destinationResponse; diff --git a/src/v0/destinations/criteo_audience/networkHandler.js b/src/v0/destinations/criteo_audience/networkHandler.js index 18bd9a93a0..6032aabcdd 100644 --- a/src/v0/destinations/criteo_audience/networkHandler.js +++ b/src/v0/destinations/criteo_audience/networkHandler.js @@ -67,7 +67,8 @@ const criteoAudienceRespHandler = (destResponse, stageMsg) => { ); }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request Processed Successfully`; const { status } = destinationResponse; if (!isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/fb/networkHandler.js b/src/v0/destinations/fb/networkHandler.js index 06235fab40..7ba5b88adc 100644 --- a/src/v0/destinations/fb/networkHandler.js +++ b/src/v0/destinations/fb/networkHandler.js @@ -2,7 +2,8 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils') const { errorResponseHandler } = require('../facebook_pixel/networkHandler'); const { prepareProxyRequest, proxyRequest } = require('../../../adapters/network'); -const destResponseHandler = (destinationResponse) => { +const destResponseHandler = (responseParams) => { + const { destinationResponse } = responseParams; errorResponseHandler(destinationResponse); return { destinationResponse: destinationResponse.response, diff --git a/src/v0/destinations/ga4/networkHandler.js b/src/v0/destinations/ga4/networkHandler.js index b62fcc8d3b..e4ca1effa8 100644 --- a/src/v0/destinations/ga4/networkHandler.js +++ b/src/v0/destinations/ga4/networkHandler.js @@ -8,7 +8,8 @@ const { isDefinedAndNotNull, isDefined, isHttpStatusSuccess } = require('../../u const tags = require('../../util/tags'); -const responseHandler = (destinationResponse, dest) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType } = responseParams; const message = `[GA4 Response Handler] - Request Processed Successfully`; let { status } = destinationResponse; const { response } = destinationResponse; @@ -29,7 +30,7 @@ const responseHandler = (destinationResponse, dest) => { // Build the error in case the validationMessages[] is non-empty const { description, validationCode, fieldPath } = response.validationMessages[0]; throw new NetworkError( - `Validation Server Response Handler:: Validation Error for ${dest} of field path :${fieldPath} | ${validationCode}-${description}`, + `Validation Server Response Handler:: Validation Error for ${destType} of field path :${fieldPath} | ${validationCode}-${description}`, status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), @@ -42,7 +43,7 @@ const responseHandler = (destinationResponse, dest) => { // if the response from destination is not a success case build an explicit error if (!isHttpStatusSuccess(status)) { throw new NetworkError( - `[GA4 Response Handler] Request failed for destination ${dest} with status: ${status}`, + `[GA4 Response Handler] Request failed for destination ${destType} with status: ${status}`, status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js index 7266154a09..b4590fb71c 100644 --- a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js @@ -102,7 +102,8 @@ const ProxyRequest = async (request) => { return response; }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = 'Request Processed Successfully'; const { status } = destinationResponse; if (isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js index 6922cde8c8..318b7802df 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js @@ -251,7 +251,8 @@ const ProxyRequest = async (request) => { return response; }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `[Google Ads Offline Conversions Response Handler] - Request processed successfully`; const { status } = destinationResponse; if (isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js index bf703ccb1b..dbd055f1a1 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js @@ -153,7 +153,8 @@ const gaAudienceRespHandler = (destResponse, stageMsg) => { ); }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request Processed Successfully`; const { status, response } = destinationResponse; if (isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/intercom/networkHandler.js b/src/v0/destinations/intercom/networkHandler.js index a4106257b3..8485dac52e 100644 --- a/src/v0/destinations/intercom/networkHandler.js +++ b/src/v0/destinations/intercom/networkHandler.js @@ -13,8 +13,9 @@ const errorResponseHandler = (destinationResponse, dest) => { } }; -const destResponseHandler = (destinationResponse, dest) => { - errorResponseHandler(destinationResponse, dest); +const destResponseHandler = (responseParams) => { + const { destinationResponse, destType } = responseParams; + errorResponseHandler(destinationResponse, destType); return { destinationResponse: destinationResponse.response, message: 'Request Processed Successfully', diff --git a/src/v0/destinations/marketo/networkHandler.js b/src/v0/destinations/marketo/networkHandler.js index 7abcc65c02..1d4b316e8d 100644 --- a/src/v0/destinations/marketo/networkHandler.js +++ b/src/v0/destinations/marketo/networkHandler.js @@ -3,9 +3,10 @@ const { marketoResponseHandler } = require('./util'); const { proxyRequest, prepareProxyRequest } = require('../../../adapters/network'); const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); -const responseHandler = (destinationResponse, destType) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType,rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; - const { status, rudderJobMetadata } = destinationResponse; + const { status } = destinationResponse; const authCache = v0Utils.getDestAuthCacheInstance(destType); // check for marketo application level failures marketoResponseHandler( diff --git a/src/v0/destinations/marketo_static_list/networkHandler.js b/src/v0/destinations/marketo_static_list/networkHandler.js index 30b053b9d3..9e73cd1f91 100644 --- a/src/v0/destinations/marketo_static_list/networkHandler.js +++ b/src/v0/destinations/marketo_static_list/networkHandler.js @@ -4,9 +4,10 @@ const v0Utils = require('../../util'); const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const { DESTINATION } = require('./config'); -const responseHandler = (destinationResponse, destType) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; - const { status, rudderJobMetadata } = destinationResponse; + const { status} = destinationResponse; const authCache = v0Utils.getDestAuthCacheInstance(destType); // check for marketo application level failures marketoResponseHandler( diff --git a/src/v0/destinations/pardot/networkHandler.js b/src/v0/destinations/pardot/networkHandler.js index 12b4abbc53..edf713ce97 100644 --- a/src/v0/destinations/pardot/networkHandler.js +++ b/src/v0/destinations/pardot/networkHandler.js @@ -65,7 +65,8 @@ const pardotRespHandler = (destResponse, stageMsg) => { ); }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = 'Request Processed Successfully'; const { status } = destinationResponse; // else successfully return status, message and original destination response diff --git a/src/v0/destinations/reddit/networkHandler.js b/src/v0/destinations/reddit/networkHandler.js index 836c015859..55087b52ac 100644 --- a/src/v0/destinations/reddit/networkHandler.js +++ b/src/v0/destinations/reddit/networkHandler.js @@ -18,7 +18,8 @@ const redditRespHandler = (destResponse) => { ); } }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request Processed Successfully`; const { status } = destinationResponse; if (!isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/salesforce/networkHandler.js b/src/v0/destinations/salesforce/networkHandler.js index 918084cc89..ac31241775 100644 --- a/src/v0/destinations/salesforce/networkHandler.js +++ b/src/v0/destinations/salesforce/networkHandler.js @@ -3,13 +3,14 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils') const { LEGACY } = require('./config'); const { salesforceResponseHandler } = require('./utils'); -const responseHandler = (destinationResponse, destType) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = `Request for destination: ${destType} Processed Successfully`; salesforceResponseHandler( destinationResponse, 'during Salesforce Response Handling', - destinationResponse?.rudderJobMetadata?.destInfo?.authKey, + rudderJobMetadata?.destInfo?.authKey, LEGACY, ); diff --git a/src/v0/destinations/salesforce_oauth/networkHandler.js b/src/v0/destinations/salesforce_oauth/networkHandler.js index 2bcace31c9..b6cbed77f9 100644 --- a/src/v0/destinations/salesforce_oauth/networkHandler.js +++ b/src/v0/destinations/salesforce_oauth/networkHandler.js @@ -3,13 +3,14 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils') const { OAUTH } = require('../salesforce/config'); const { salesforceResponseHandler } = require('../salesforce/utils'); -const responseHandler = (destinationResponse, destType) => { +const responseHandler = (responseParams) => { + const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = `Request for destination: ${destType} Processed Successfully`; salesforceResponseHandler( destinationResponse, 'during Salesforce Response Handling', - destinationResponse?.rudderJobMetadata?.destInfo?.authKey, + rudderJobMetadata?.destInfo?.authKey, OAUTH, ); diff --git a/src/v0/destinations/snapchat_custom_audience/networkHandler.js b/src/v0/destinations/snapchat_custom_audience/networkHandler.js index db36f6f518..feedaea3e3 100644 --- a/src/v0/destinations/snapchat_custom_audience/networkHandler.js +++ b/src/v0/destinations/snapchat_custom_audience/networkHandler.js @@ -80,7 +80,8 @@ const scaAudienceRespHandler = (destResponse, stageMsg) => { ); }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = `Request Processed Successfully`; const { status } = destinationResponse; if (isHttpStatusSuccess(status)) { diff --git a/src/v0/destinations/the_trade_desk/networkHandler.js b/src/v0/destinations/the_trade_desk/networkHandler.js index ca5ac68be8..f04d301e3b 100644 --- a/src/v0/destinations/the_trade_desk/networkHandler.js +++ b/src/v0/destinations/the_trade_desk/networkHandler.js @@ -41,7 +41,8 @@ const proxyRequest = async (request) => { return response; }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const message = 'Request Processed Successfully'; const { response, status } = destinationResponse; diff --git a/src/v0/destinations/tiktok_ads/networkHandler.js b/src/v0/destinations/tiktok_ads/networkHandler.js index ae93b1ec15..5d4b7fd4e0 100644 --- a/src/v0/destinations/tiktok_ads/networkHandler.js +++ b/src/v0/destinations/tiktok_ads/networkHandler.js @@ -8,7 +8,8 @@ const { DESTINATION } = require('./config'); const { TAG_NAMES } = require('../../util/tags'); const { HTTP_STATUS_CODES } = require('../../util/constant'); -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const msg = `[${DESTINATION} Response Handler] - Request Processed Successfully`; const { response: { code }, diff --git a/src/v0/util/facebookUtils/networkHandler.js b/src/v0/util/facebookUtils/networkHandler.js index e0d69fa5c8..52488ef3e4 100644 --- a/src/v0/util/facebookUtils/networkHandler.js +++ b/src/v0/util/facebookUtils/networkHandler.js @@ -249,7 +249,8 @@ const errorResponseHandler = (destResponse) => { ); }; -const destResponseHandler = (destinationResponse) => { +const destResponseHandler = (responseParams) => { + const { destinationResponse } = responseParams; errorResponseHandler(destinationResponse); return { destinationResponse: destinationResponse.response, diff --git a/src/v1/destinations/campaign_manager/networkHandler.js b/src/v1/destinations/campaign_manager/networkHandler.js index 431cbd6966..79f7e7f93b 100644 --- a/src/v1/destinations/campaign_manager/networkHandler.js +++ b/src/v1/destinations/campaign_manager/networkHandler.js @@ -34,10 +34,11 @@ function isEventAbortableAndExtractErrMsg(element, proxyOutputObj) { return isAbortable; } -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse, rudderJobMetadata } = responseParams; const message = `[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully`; const responseWithIndividualEvents = []; - const { response, status, rudderJobMetadata } = destinationResponse; + const { response, status } = destinationResponse; if (isHttpStatusSuccess(status)) { // check for Partial Event failures and Successes From b1327ebdb049163b3c5f046cb4605518e99481f3 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Thu, 25 Jan 2024 14:34:49 +0530 Subject: [PATCH 02/37] feat: update proxy v1 test cases --- .../campaign_manager/dataDelivery/data.ts | 80 +++++++------------ .../salesforce/dataDelivery/data.ts | 50 ------------ 2 files changed, 27 insertions(+), 103 deletions(-) diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts index 601ad56401..e84b3b7514 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts @@ -415,34 +415,6 @@ export const data = [ kind: 'dfareporting#conversionsBatchInsertResponse', }, status: 200, - rudderJobMetadata: [ - { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - { - jobId: 3, - attemptNum: 1, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - ], }, response: [ { @@ -530,19 +502,21 @@ export const data = [ XML: {}, FORM: {}, }, - metadata: { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', + metadata: [ + { + jobId: 2, + attemptNum: 0, + userId: '', + sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', + destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: { + access_token: 'secret', + refresh_token: 'refresh', + developer_token: 'developer_Token', + }, }, - }, + ], files: {}, }, method: 'POST', @@ -576,22 +550,22 @@ export const data = [ kind: 'dfareporting#conversionsBatchInsertResponse', }, status: 200, - rudderJobMetadata: { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, }, response: [ { + metadata: { + jobId: 2, + attemptNum: 0, + userId: '', + sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', + destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', + workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', + secret: { + access_token: 'secret', + refresh_token: 'refresh', + developer_token: 'developer_Token', + }, + }, error: 'success', statusCode: 200, }, diff --git a/test/integrations/destinations/salesforce/dataDelivery/data.ts b/test/integrations/destinations/salesforce/dataDelivery/data.ts index 2f1e04815b..cfaa75e23e 100644 --- a/test/integrations/destinations/salesforce/dataDelivery/data.ts +++ b/test/integrations/destinations/salesforce/dataDelivery/data.ts @@ -58,11 +58,6 @@ export const data = [ statusText: 'No Content', }, status: 204, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, }, }, @@ -128,11 +123,6 @@ export const data = [ errorCode: 'INVALID_SESSION_ID', }, ], - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, status: 401, }, statTags: { @@ -210,11 +200,6 @@ export const data = [ }, ], status: 401, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -291,11 +276,6 @@ export const data = [ }, ], status: 403, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -372,11 +352,6 @@ export const data = [ }, ], status: 503, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -451,11 +426,6 @@ export const data = [ error_description: 'authentication failure', }, status: 400, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -526,11 +496,6 @@ export const data = [ errorCode: 'SERVER_UNAVAILABLE', message: 'Server Unavailable', }, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, status: 503, }, message: @@ -619,11 +584,6 @@ export const data = [ ], }, status: 200, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, }, }, @@ -685,11 +645,6 @@ export const data = [ destinationResponse: { response: '[ECONNABORTED] :: Connection aborted', status: 500, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', @@ -783,11 +738,6 @@ export const data = [ destinationResponse: { response: '[EAI_AGAIN] :: Temporary failure in name resolution', status: 500, - rudderJobMetadata: { - destInfo: { - authKey: '2HezPl1w11opbFSxnLDEgZ7kWTf', - }, - }, }, statTags: { destType: 'SALESFORCE', From 9dd862540cc8e4e56b9bc638cc1da62e5f19c45f Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Mon, 29 Jan 2024 17:48:02 +0530 Subject: [PATCH 03/37] feat: update proxy tests for cm360 Added new structure for proxy test scnearios for cm360 also added zod validations as part of tests --- package-lock.json | 11 +- package.json | 3 +- src/types/zodTypes.ts | 172 +++++ test/integrations/common/google/network.ts | 109 ++++ test/integrations/common/network.ts | 62 ++ test/integrations/component.test.ts | 6 +- .../campaign_manager/dataDelivery/business.ts | 605 ++++++++++++++++++ .../campaign_manager/dataDelivery/data.ts | 586 +---------------- .../campaign_manager/dataDelivery/oauth.ts | 557 ++++++++++++++++ .../campaign_manager/dataDelivery/other.ts | 533 +++++++++++++++ .../destinations/campaign_manager/network.ts | 302 +++------ test/integrations/testTypes.ts | 3 + test/integrations/testUtils.ts | 132 ++++ 13 files changed, 2276 insertions(+), 805 deletions(-) create mode 100644 src/types/zodTypes.ts create mode 100644 test/integrations/common/google/network.ts create mode 100644 test/integrations/common/network.ts create mode 100644 test/integrations/destinations/campaign_manager/dataDelivery/business.ts create mode 100644 test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts create mode 100644 test/integrations/destinations/campaign_manager/dataDelivery/other.ts diff --git a/package-lock.json b/package-lock.json index 1c40b23fba..38d6642508 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,8 @@ "ua-parser-js": "^1.0.37", "unset-value": "^2.0.1", "uuid": "^9.0.0", - "valid-url": "^1.0.9" + "valid-url": "^1.0.9", + "zod": "^3.22.4" }, "devDependencies": { "@commitlint/config-conventional": "^17.6.3", @@ -21072,6 +21073,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 43aa0d9890..09323e35c9 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,8 @@ "ua-parser-js": "^1.0.37", "unset-value": "^2.0.1", "uuid": "^9.0.0", - "valid-url": "^1.0.9" + "valid-url": "^1.0.9", + "zod": "^3.22.4" }, "devDependencies": { "@commitlint/config-conventional": "^17.6.3", diff --git a/src/types/zodTypes.ts b/src/types/zodTypes.ts new file mode 100644 index 0000000000..f3b9c57dd4 --- /dev/null +++ b/src/types/zodTypes.ts @@ -0,0 +1,172 @@ +import { z } from 'zod'; +import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib'; +import { isHttpStatusSuccess } from '../v0/util'; + +export const ProxyMetadataSchema = z.object({ + jobId: z.number(), + attemptNum: z.number(), + userId: z.string(), + sourceId: z.string(), + destinationId: z.string(), + workspaceId: z.string(), + secret: z.record(z.unknown()), + destInfo: z.object({}).optional(), + omitempty: z.record(z.unknown()).optional(), + dontBatch: z.boolean(), +}); + +export const ProxyV0RequestSchema = z.object({ + version: z.string(), + type: z.string(), + method: z.string(), + endpoint: z.string(), + userId: z.string(), + headers: z.record(z.unknown()).optional(), + params: z.record(z.unknown()).optional(), + body: z + .object({ + JSON: z.record(z.unknown()).optional(), + JSON_ARRAY: z.record(z.unknown()).optional(), + XML: z.record(z.unknown()).optional(), + FORM: z.record(z.unknown()).optional(), + }) + .optional(), + files: z.record(z.unknown()).optional(), + metadata: ProxyMetadataSchema, +}); + +export const ProxyV1RequestSchema = z.object({ + version: z.string(), + type: z.string(), + method: z.string(), + endpoint: z.string(), + userId: z.string(), + headers: z.record(z.unknown()).optional(), + params: z.record(z.unknown()).optional(), + body: z + .object({ + JSON: z.record(z.unknown()).optional(), + JSON_ARRAY: z.record(z.unknown()).optional(), + XML: z.record(z.unknown()).optional(), + FORM: z.record(z.unknown()).optional(), + }) + .optional(), + files: z.record(z.unknown()).optional(), + metadata: z.array(ProxyMetadataSchema), + destinationConfig: z.record(z.unknown()), +}); + +export const DeliveryV0ResponseSchema = z + .object({ + status: z.number(), + message: z.string(), + destinationResponse: z.unknown(), + statTags: z.record(z.unknown()).optional(), + authErrorCategory: z.string().optional(), + }) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.statTags); + } + return true; + }, + { + // eslint-disable-next-line sonarjs/no-duplicate-string + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }, + ); + +export const DeliveryV0ResponseSchemaForOauth = z + .object({ + status: z.number(), + message: z.string(), + destinationResponse: z.unknown(), + statTags: z.record(z.unknown()).optional(), + authErrorCategory: z.string().optional(), + }) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.statTags); + } + return true; + }, + { + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }, + ) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.authErrorCategory); + } + return true; + }, + { + message: "authErrorCategory can't be empty when status is not a 2XX", + path: ['authErrorCategory'], // Pointing out which field is invalid + }, + ); + +const DeliveryJobStateSchema = z.object({ + error: z.string(), + statusCode: z.number(), + metadata: ProxyMetadataSchema, +}); + +export const DeliveryV1ResponseSchema = z + .object({ + status: z.number(), + message: z.string(), + statTags: z.record(z.unknown()).optional(), + authErrorCategory: z.string().optional(), + response: z.array(DeliveryJobStateSchema), + }) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.statTags); + } + return true; + }, + { + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }, + ); + +export const DeliveryV1ResponseSchemaForOauth = z + .object({ + status: z.number(), + message: z.string(), + statTags: z.record(z.unknown()).optional(), + authErrorCategory: z.string().optional(), + response: z.array(DeliveryJobStateSchema), + }) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.statTags); + } + return true; + }, + { + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }, + ) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.authErrorCategory); + } + return true; + }, + { + message: "authErrorCategory can't be empty when status is not a 2XX", + path: ['authErrorCategory'], // Pointing out which field is invalid + }, + ); diff --git a/test/integrations/common/google/network.ts b/test/integrations/common/google/network.ts new file mode 100644 index 0000000000..95b76f8da8 --- /dev/null +++ b/test/integrations/common/google/network.ts @@ -0,0 +1,109 @@ +// Ads API +// Ref: https://developers.google.com/google-ads/api/docs/get-started/common-errors + +export const networkCallsData = [ + { + description: 'Mock response depicting CREDENTIALS_MISSING error', + httpReq: { + method: 'post', + url: 'https://googleapis.com/test_url_for_credentials_missing', + }, + httpRes: { + data: { + 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', + }, + }, + ], + }, + }, + status: 401, + }, + }, + { + description: 'Mock response depicting ACCESS_TOKEN_SCOPE_INSUFFICIENT error', + httpReq: { + method: 'post', + url: 'https://googleapis.com/test_url_for_access_token_scope_insufficient', + }, + httpRes: { + data: { + 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', + }, + }, + ], + }, + }, + status: 403, + }, + }, + { + description: 'Mock response for google.auth.exceptions.RefreshError invalid_grant error', + httpReq: { + method: 'post', + url: 'https://googleapis.com/test_url_for_invalid_grant', + }, + httpRes: { + data: { + error: { + code: 403, + message: 'invalid_grant', + error_description: 'Bad accesss', + }, + }, + status: 403, + }, + }, + { + description: 'Mock response for google.auth.exceptions.RefreshError refresh_token error', + httpReq: { + method: 'post', + url: 'https://googleapis.com/test_url_for_refresh_error', + }, + httpRes: { + data: { + error: 'unauthorized', + error_description: 'Access token expired: 2020-10-20T12:00:00.000Z', + }, + status: 401, + }, + }, +]; diff --git a/test/integrations/common/network.ts b/test/integrations/common/network.ts new file mode 100644 index 0000000000..8f80e406ae --- /dev/null +++ b/test/integrations/common/network.ts @@ -0,0 +1,62 @@ +export const networkCallsData = [ + { + description: 'Mock response depicting SERVICE NOT AVAILABLE error', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_service_not_available', + }, + httpRes: { + data: { + 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, + }, + }, + { + description: 'Mock response depicting INTERNAL SERVER ERROR error', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_internal_server_error', + }, + httpRes: { + data: 'Internal Server Error', + status: 500, + }, + }, + { + description: 'Mock response depicting GATEWAY TIME OUT error', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_gateway_time_out', + }, + httpRes: { + data: 'Gateway Timeout', + status: 504, + }, + }, + { + description: 'Mock response depicting null response', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_null_response', + }, + httpRes: { + data: null, + status: 500, + }, + }, + { + description: 'Mock response depicting null and no status', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_null_and_no_status', + }, + httpRes: { + data: null, + }, + }, +]; diff --git a/test/integrations/component.test.ts b/test/integrations/component.test.ts index ec4fb02dc1..aaaa536d91 100644 --- a/test/integrations/component.test.ts +++ b/test/integrations/component.test.ts @@ -16,6 +16,7 @@ import { getMockHttpCallsData, getAllTestMockDataFilePaths, addMock, + validateTestWithZOD, } from './testUtils'; import tags from '../../src/v0/util/tags'; import { Server } from 'http'; @@ -53,7 +54,7 @@ if (opts.generate === 'true') { let server: Server; -const REPORT_COMPATIBLE_INTEGRATION = ['klaviyo']; +const INTEGRATIONS_WITH_UPDATED_TEST_STRUCTURE = ['klaviyo', 'campaign_manager']; beforeAll(async () => { initaliseReport(); @@ -147,7 +148,8 @@ const testRoute = async (route, tcData: TestCaseData) => { expect(response.status).toEqual(outputResp.status); - if (REPORT_COMPATIBLE_INTEGRATION.includes(tcData.name?.toLocaleLowerCase())) { + if (INTEGRATIONS_WITH_UPDATED_TEST_STRUCTURE.includes(tcData.name?.toLocaleLowerCase())) { + expect(validateTestWithZOD(tcData, response)).toEqual(true); const bodyMatched = _.isEqual(response.body, outputResp.body); const statusMatched = response.status === outputResp.status; if (bodyMatched && statusMatched) { diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts new file mode 100644 index 0000000000..9c62f55387 --- /dev/null +++ b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts @@ -0,0 +1,605 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils'; + +// Boilerplat data for the test cases +// ====================================== + +const commonHeaders = { + Authorization: 'Bearer dummyApiKey', + 'Content-Type': 'application/json', +}; + +const encryptionInfo = { + kind: 'dfareporting#encryptionInfo', + encryptionSource: 'AD_SERVING', + encryptionEntityId: '3564523', + encryptionEntityType: 'DCM_ACCOUNT', +}; + +const testConversion1 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const testConversion2 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: { + kind: 'dfareporting#conversionsBatchInsertRequest', + encryptionInfo, + conversions: [testConversion1, testConversion2], + }, +}; + +const proxyMetdata1: ProxyMetdata = { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, +}; + +const proxyMetdata2: ProxyMetdata = { + jobId: 2, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, +}; + +const metadataArray = [proxyMetdata1, proxyMetdata2]; + +// Test scenarios for the test cases +// =================================== + +export const testScneariosForV0API = [ + { + id: 'cm360_v0_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 without any error', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://dfareporting.googleapis.com/test_url_for_valid_request', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: '[CAMPAIGN_MANAGER Response Handler] - Request Processed Successfully', + destinationResponse: { + response: { + hasFailures: false, + status: [ + { + conversion: testConversion1, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: testConversion2, + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_scenario_2', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 with error for conversion 2', + successCriteria: 'Should return 400 with error and with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://dfareporting.googleapis.com/test_url_for_invalid_request_conversion_2', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + status: 400, + message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation', + destinationResponse: { + response: { + hasFailures: true, + status: [ + { + conversion: testConversion1, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: testConversion2, + errors: [ + { + code: 'NOT_FOUND', + message: 'Floodlight config id: 213123123 was not found.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_scenario_3', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 with error for both conversions', + successCriteria: 'Should return 400 with error and with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: + 'https://dfareporting.googleapis.com/test_url_for_invalid_request_both_conversions', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + status: 400, + message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation', + destinationResponse: { + response: { + hasFailures: true, + status: [ + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'INVALID_ARGUMENT', + message: 'Gclid is not valid.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'NOT_FOUND', + message: 'Floodlight config id: 213123123 was not found.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, +]; + +export const testScneariosForV1API = [ + { + id: 'cm360_v1_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Test for a valid request - where the destination responds with 200 without any error', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://dfareporting.googleapis.com/test_url_for_valid_request', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', + destinationResponse: { + response: { + hasFailures: false, + status: [ + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, + }, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, + }, + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + response: [ + { + statusCode: 200, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'success', + }, + { + statusCode: 200, + metadata: { + jobId: 2, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'success', + }, + ], + }, + }, + }, + }, + }, + { + id: 'cm360_v1_scenario_2', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Test for a valid request - where the destination responds with 200 with error for conversion 2', + successCriteria: 'Should return 200 with partial failures within the response payload', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: + 'https://dfareporting.googleapis.com/test_url_for_invalid_request_conversion_2', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', + destinationResponse: { + response: { + hasFailures: true, + status: [ + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, + }, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'NOT_FOUND', + message: 'Floodlight config id: 213123123 was not found.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + response: [ + { + statusCode: 200, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'success', + }, + { + statusCode: 400, + metadata: { + jobId: 2, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'Floodlight config id: 213123123 was not found., ', + }, + ], + }, + }, + }, + }, + }, + { + id: 'cm360_v1_scenario_3', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 with error for both conversions', + successCriteria: 'Should return 200 with all failures within the response payload', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: + 'https://dfareporting.googleapis.com/test_url_for_invalid_request_both_conversions', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', + destinationResponse: { + response: { + hasFailures: true, + status: [ + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'INVALID_ARGUMENT', + message: 'Gclid is not valid.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + { + conversion: { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, + }, + errors: [ + { + code: 'NOT_FOUND', + message: 'Floodlight config id: 213123123 was not found.', + kind: 'dfareporting#conversionError', + }, + ], + kind: 'dfareporting#conversionStatus', + }, + ], + kind: 'dfareporting#conversionsBatchInsertResponse', + }, + status: 200, + }, + response: [ + { + statusCode: 400, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'Gclid is not valid., ', + }, + { + statusCode: 400, + metadata: { + jobId: 2, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + error: 'Floodlight config id: 213123123 was not found., ', + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts index e84b3b7514..994ec0a2ee 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts @@ -1,578 +1,12 @@ +import { testScneariosForV0API, testScneariosForV1API } from './business'; +import { v0oauthScenarios, v1oauthScenarios } from './oauth'; +import { otherScenariosV0, otherScenariosV1 } from './other'; + export const data = [ - { - name: 'campaign_manager', - description: 'Sucess insert request V0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437689/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: '[CAMPAIGN_MANAGER Response Handler] - Request Processed Successfully', - destinationResponse: { - response: { - hasFailures: false, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'campaign_manager', - description: 'Failure insert request', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437690/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - status: 400, - message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation', - statTags: { - errorCategory: 'network', - errorType: 'aborted', - destType: 'CAMPAIGN_MANAGER', - module: 'destination', - implementation: 'native', - feature: 'dataDelivery', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - }, - destinationResponse: { - response: { - hasFailures: true, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - errors: [ - { - code: 'NOT_FOUND', - message: 'Floodlight config id: 213123123 was not found.', - kind: 'dfareporting#conversionError', - }, - ], - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'campaign_manager', - description: 'Failure insert request Aborted', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437691/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - status: 400, - message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation', - statTags: { - errorCategory: 'network', - errorType: 'aborted', - destType: 'CAMPAIGN_MANAGER', - module: 'destination', - implementation: 'native', - feature: 'dataDelivery', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - }, - destinationResponse: { - response: { - hasFailures: true, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - errors: [ - { - code: 'INVALID_ARGUMENT', - message: 'Floodlight config id: 213123123 was not found.', - kind: 'dfareporting#conversionError', - }, - ], - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'campaign_manager', - description: 'Sucess and fail insert request v1', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437692/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 8, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - metadata: [ - { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - { - jobId: 3, - attemptNum: 1, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - ], - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', - destinationResponse: { - response: { - hasFailures: true, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - errors: [ - { - code: 'INVALID_ARGUMENT', - kind: 'dfareporting#conversionError', - message: 'Floodlight config id: 213123123 was not found.', - }, - ], - }, - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 8, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - }, - response: [ - { - error: 'Floodlight config id: 213123123 was not found., ', - statusCode: 400, - metadata: { - attemptNum: 0, - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - jobId: 2, - secret: { - access_token: 'secret', - developer_token: 'developer_Token', - refresh_token: 'refresh', - }, - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - userId: '', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - }, - }, - { - error: 'success', - metadata: { - attemptNum: 1, - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - jobId: 3, - secret: { - access_token: 'secret', - developer_token: 'developer_Token', - refresh_token: 'refresh', - }, - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - userId: '', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - }, - statusCode: 200, - }, - ], - }, - }, - }, - }, - }, - { - name: 'campaign_manager', - description: 'Sucess insert request v1', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/43770/conversions/batchinsert', - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - metadata: [ - { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - ], - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully', - destinationResponse: { - response: { - hasFailures: false, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - }, - response: [ - { - metadata: { - jobId: 2, - attemptNum: 0, - userId: '', - sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr', - destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L', - workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', - secret: { - access_token: 'secret', - refresh_token: 'refresh', - developer_token: 'developer_Token', - }, - }, - error: 'success', - statusCode: 200, - }, - ], - }, - }, - }, - }, - }, + ...testScneariosForV0API, + ...testScneariosForV1API, + ...v0oauthScenarios, + ...v1oauthScenarios, + ...otherScenariosV0, + ...otherScenariosV1, ]; diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts new file mode 100644 index 0000000000..1b70a9e48f --- /dev/null +++ b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts @@ -0,0 +1,557 @@ +import { generateProxyV1Payload, generateProxyV0Payload } from '../../../testUtils'; +// Boilerplat data for the test cases +// ====================================== + +const commonHeaders = { + Authorization: 'Bearer dummyApiKey', + 'Content-Type': 'application/json', +}; + +const encryptionInfo = { + kind: 'dfareporting#encryptionInfo', + encryptionSource: 'AD_SERVING', + encryptionEntityId: '3564523', + encryptionEntityType: 'DCM_ACCOUNT', +}; + +const testConversion1 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const testConversion2 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: { + kind: 'dfareporting#conversionsBatchInsertRequest', + encryptionInfo, + conversions: [testConversion1, testConversion2], + }, +}; + +// Test scenarios for the test cases +// =================================== + +export const v0oauthScenarios = [ + { + id: 'cm360_v0_oauth_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Oauth scneario where valid credentials are missing as mock response from destination', + successCriteria: + 'Since the error from the destination is 401 - the proxy should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_credentials_missing', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: 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. during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + 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', + }, + }, + ], + }, + }, + status: 401, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'REFRESH_TOKEN', + }, + }, + }, + }, + }, + { + id: 'cm360_v0_oauth_scenario_2', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Oauth scneario where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_access_token_scope_insufficient', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: Request had insufficient authentication scopes. during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + 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', + }, + }, + ], + }, + }, + status: 403, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'AUTH_STATUS_INACTIVE', + }, + }, + }, + }, + }, + { + id: 'cm360_v0_oauth_scenario_3', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Oauth scneario where google.auth.exceptions.RefreshError invalid_grant error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_invalid_grant', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: invalid_grant during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + error: { + code: 403, + message: 'invalid_grant', + error_description: 'Bad accesss', + }, + }, + status: 403, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'AUTH_STATUS_INACTIVE', + }, + }, + }, + }, + }, + { + id: 'cm360_v0_oauth_scenario_4', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Oauth scneario where google.auth.exceptions.RefreshError refresh error as mock response from destination', + successCriteria: 'Should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_refresh_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + error: 'unauthorized', + error_description: 'Access token expired: 2020-10-20T12:00:00.000Z', + }, + status: 401, + }, + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'REFRESH_TOKEN', + }, + }, + }, + }, + }, +]; + +export const v1oauthScenarios = [ + { + id: 'cm360_v1_oauth_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Oauth scneario where valid credentials are missing as mock response from destination', + successCriteria: + 'Since the error from the destination is 401 - the proxy should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_credentials_missing', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + 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, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'REFRESH_TOKEN', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_oauth_scenario_2', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Oauth scneario where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_access_token_scope_insufficient', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + 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, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'AUTH_STATUS_INACTIVE', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_oauth_scenario_3', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Oauth scneario where google.auth.exceptions.RefreshError invalid_grant error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_invalid_grant', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + response: [ + { + error: + '{"error":{"code":403,"message":"invalid_grant","error_description":"Bad accesss"}}', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'AUTH_STATUS_INACTIVE', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_oauth_scenario_4', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Oauth scneario where google.auth.exceptions.RefreshError refresh error as mock response from destination', + successCriteria: 'Should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://googleapis.com/test_url_for_refresh_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + response: [ + { + error: + '{"error":"unauthorized","error_description":"Access token expired: 2020-10-20T12:00:00.000Z"}', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'aborted', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: 'REFRESH_TOKEN', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts new file mode 100644 index 0000000000..b1b1337680 --- /dev/null +++ b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts @@ -0,0 +1,533 @@ +import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils'; + +export const otherScenariosV0 = [ + { + id: 'cm360_v0_other_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Scneario for testing Service Unavailable error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_service_not_available', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: Service Unavailable during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: { + 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, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_other_scenario_2', + name: 'campaign_manager', + description: '[Proxy v0 API] :: Scneario for testing Internal Server error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_internal_server_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: 'Internal Server Error', + status: 500, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_other_scenario_3', + name: 'campaign_manager', + description: '[Proxy v0 API] :: Scneario for testing Gateway Time Out error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_gateway_time_out', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: 'Gateway Timeout', + status: 504, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_other_scenario_4', + name: 'campaign_manager', + description: '[Proxy v0 API] :: Scneario for testing null response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_null_response', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: '', + status: 500, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + id: 'cm360_v0_other_scenario_5', + name: 'campaign_manager', + description: + '[Proxy v0 API] :: Scneario 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: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + endpoint: 'https://random_test_url/test_for_null_and_no_status', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + message: + 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3', + destinationResponse: { + response: '', + status: 500, + }, + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, +]; + +export const otherScenariosV1 = [ + { + id: 'cm360_v1_other_scenario_1', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Scneario 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: 500, + 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: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: '', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_other_scenario_2', + name: 'campaign_manager', + description: '[Proxy v1 API] :: Scneario 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: 500, + body: { + output: { + response: [ + { + error: '"Internal Server Error"', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: '', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_other_scenario_3', + name: 'campaign_manager', + description: '[Proxy v1 API] :: Scneario 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: 500, + body: { + output: { + response: [ + { + error: '"Gateway Timeout"', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: '', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_other_scenario_4', + name: 'campaign_manager', + description: '[Proxy v1 API] :: Scneario 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: 500, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: '', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'cm360_v1_other_scenario_5', + name: 'campaign_manager', + description: + '[Proxy v1 API] :: Scneario 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: 500, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + }, + ], + statTags: { + errorCategory: 'network', + errorType: 'retryable', + destType: 'CAMPAIGN_MANAGER', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + }, + authErrorCategory: '', + message: + 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/campaign_manager/network.ts b/test/integrations/destinations/campaign_manager/network.ts index ddecbaf8fa..b7c2301248 100644 --- a/test/integrations/destinations/campaign_manager/network.ts +++ b/test/integrations/destinations/campaign_manager/network.ts @@ -1,49 +1,70 @@ -const Data = [ +const commonHeaders = { + Authorization: 'Bearer dummyApiKey', + 'Content-Type': 'application/json', +}; + +const encryptionInfo = { + kind: 'dfareporting#encryptionInfo', + encryptionSource: 'AD_SERVING', + encryptionEntityId: '3564523', + encryptionEntityType: 'DCM_ACCOUNT', +}; + +const testConversion1 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 7, + gclid: '123', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const testConversion2 = { + timestampMicros: '1668624722000000', + floodlightConfigurationId: '213123123', + ordinal: '1', + floodlightActivityId: '456543345245', + value: 8, + gclid: '321', + limitAdTracking: true, + childDirectedTreatment: true, +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: { + kind: 'dfareporting#conversionsBatchInsertRequest', + encryptionInfo, + conversions: [testConversion1, testConversion2], + }, +}; + +// MOCK DATA +const businessMockData = [ { + description: 'Mock response from destination depicting a valid request', httpReq: { method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437689/conversions/batchinsert', + url: 'https://dfareporting.googleapis.com/test_url_for_valid_request', data: { kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', + encryptionInfo, + conversions: [testConversion1, testConversion2], }, + headers: commonHeaders, }, httpRes: { data: { hasFailures: false, status: [ { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, + conversion: testConversion1, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: testConversion2, kind: 'dfareporting#conversionStatus', }, ], @@ -54,50 +75,28 @@ const Data = [ }, }, { + description: + 'Mock response from destination depicting a request with 1 valid and 1 invalid conversion', httpReq: { method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437690/conversions/batchinsert', + url: 'https://dfareporting.googleapis.com/test_url_for_invalid_request_conversion_2', data: { kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', + encryptionInfo, + conversions: [testConversion1, testConversion2], }, + headers: commonHeaders, }, httpRes: { data: { hasFailures: true, status: [ { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, + conversion: testConversion1, + kind: 'dfareporting#conversionStatus', + }, + { + conversion: testConversion2, errors: [ { code: 'NOT_FOUND', @@ -115,185 +114,37 @@ const Data = [ }, }, { + description: 'Mock response from destination depicting a request with 2 invalid conversions', httpReq: { method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/43770/conversions/batchinsert', - data: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - }, - httpRes: { - data: { - hasFailures: false, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - statusText: 'OK', - }, - }, - { - httpReq: { - method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437692/conversions/batchinsert', + url: 'https://dfareporting.googleapis.com/test_url_for_invalid_request_both_conversions', data: { kind: 'dfareporting#conversionsBatchInsertRequest', - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 8, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', + encryptionInfo, + conversions: [testConversion1, testConversion2], }, + headers: commonHeaders, }, httpRes: { data: { hasFailures: true, status: [ { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, + conversion: testConversion1, errors: [ { code: 'INVALID_ARGUMENT', - message: 'Floodlight config id: 213123123 was not found.', + message: 'Gclid is not valid.', kind: 'dfareporting#conversionError', }, ], kind: 'dfareporting#conversionStatus', }, { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 8, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - kind: 'dfareporting#conversionStatus', - }, - ], - kind: 'dfareporting#conversionsBatchInsertResponse', - }, - status: 200, - statusText: 'OK', - }, - }, - { - httpReq: { - method: 'post', - url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437691/conversions/batchinsert', - data: { - kind: 'dfareporting#conversionsBatchInsertRequest', - encryptionInfo: { - kind: 'dfareporting#encryptionInfo', - encryptionSource: 'AD_SERVING', - encryptionEntityId: '3564523', - encryptionEntityType: 'DCM_ACCOUNT', - }, - conversions: [ - { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, - ], - }, - headers: { - Authorization: 'Bearer dummyApiKey', - 'Content-Type': 'application/json', - }, - }, - httpRes: { - data: { - hasFailures: true, - status: [ - { - conversion: { - timestampMicros: '1668624722000000', - floodlightConfigurationId: '213123123', - ordinal: '1', - floodlightActivityId: '456543345245', - value: 7, - gclid: '123', - limitAdTracking: true, - childDirectedTreatment: true, - }, + conversion: testConversion2, errors: [ { - code: 'INVALID_ARGUMENT', + code: 'NOT_FOUND', message: 'Floodlight config id: 213123123 was not found.', kind: 'dfareporting#conversionError', }, @@ -308,4 +159,5 @@ const Data = [ }, }, ]; -export const networkCallsData = [...Data]; + +export const networkCallsData = [...businessMockData]; diff --git a/test/integrations/testTypes.ts b/test/integrations/testTypes.ts index 51667e8044..f181b00139 100644 --- a/test/integrations/testTypes.ts +++ b/test/integrations/testTypes.ts @@ -31,6 +31,9 @@ export interface mockType { export interface TestCaseData { name: string; description: string; + scenario?: string; + successCriteria?: string; + comment?: string; feature: string; module: string; version?: string; diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index 09f3a82d40..a761170afc 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -1,3 +1,4 @@ +import { z } from 'zod'; import { globSync } from 'glob'; import { join } from 'path'; import { MockHttpCallsData, TestCaseData } from './testTypes'; @@ -5,6 +6,15 @@ import MockAdapter from 'axios-mock-adapter'; import isMatch from 'lodash/isMatch'; import { OptionValues } from 'commander'; import { removeUndefinedAndNullValues } from '@rudderstack/integrations-lib'; +import { ProxyMetdata } from '../../src/types'; +import { + DeliveryV0ResponseSchema, + DeliveryV0ResponseSchemaForOauth, + DeliveryV1ResponseSchema, + DeliveryV1ResponseSchemaForOauth, + ProxyV0RequestSchema, + ProxyV1RequestSchema, +} from '../../src/types/zodTypes'; const generateAlphanumericId = (size = 36) => [...Array(size)].map(() => ((Math.random() * size) | 0).toString(size)).join(''); @@ -32,7 +42,9 @@ export const getAllTestMockDataFilePaths = (dirPath: string, destination: string const globPattern = join(dirPath, '**', 'network.ts'); let testFilePaths = globSync(globPattern); if (destination) { + const commonTestFilePaths = testFilePaths.filter((testFile) => testFile.includes('common')); testFilePaths = testFilePaths.filter((testFile) => testFile.includes(destination)); + testFilePaths = [...commonTestFilePaths, ...testFilePaths]; } return testFilePaths; }; @@ -364,3 +376,123 @@ export const compareObjects = (obj1, obj2, logPrefix = '', differences: string[] return differences; }; + +export const generateProxyV0Payload = (payloadParameters: any, metadataInput?: ProxyMetdata) => { + let metadata: ProxyMetdata = { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }; + if (metadataInput) { + metadata = metadataInput; + } + const payload = { + version: 'v0', + type: 'REST', + userId: payloadParameters.userId || 'default-userId', + method: payloadParameters.method || 'POST', + endpoint: payloadParameters.endpoint || '', + headers: payloadParameters.headers || {}, + params: payloadParameters.params || {}, + body: { + JSON: payloadParameters.JSON || {}, + JSON_ARRAY: payloadParameters.JSON_ARRAY || {}, + XML: payloadParameters.XML || {}, + FORM: payloadParameters.FORM || {}, + }, + files: payloadParameters.files || {}, + metadata, + }; + return removeUndefinedAndNullValues(payload); +}; + +export const generateProxyV1Payload = ( + payloadParameters: any, + metadataInput?: ProxyMetdata[], + destinationConfig?: any, +) => { + let metadata: ProxyMetdata[] = [ + { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, + }, + ]; + if (metadataInput) { + metadata = metadataInput; + } + const payload = { + version: 'v1', + type: 'REST', + userId: payloadParameters.userId || 'default-userId', + method: payloadParameters.method || 'POST', + endpoint: payloadParameters.endpoint || '', + headers: payloadParameters.headers || {}, + params: payloadParameters.params || {}, + body: { + JSON: payloadParameters.JSON || {}, + JSON_ARRAY: payloadParameters.JSON_ARRAY || {}, + XML: payloadParameters.XML || {}, + FORM: payloadParameters.FORM || {}, + }, + files: payloadParameters.files || {}, + metadata, + destinationConfig: destinationConfig || {}, + }; + return removeUndefinedAndNullValues(payload); +}; + +// ----------------------------- +// Zod validations + +export const validateTestWithZOD = (testPayload: TestCaseData, response: any) => { + // Validate the resquest payload + switch (testPayload.feature) { + // case 'router': + // RouterSchema.parse(responseBody); + // break; + // case 'batch': + // BatchScheam.parse(responseBody); + // break; + // case 'user_deletion': + // DeletionSchema.parse(responseBody); + // break; + // case 'processor': + // ProcessorSchema.parse(responseBody); + // break; + case 'dataDelivery': + if (testPayload.version === 'v0') { + ProxyV0RequestSchema.parse(testPayload.input.request.body); + if (testPayload.scenario === 'Oauth') { + DeliveryV0ResponseSchemaForOauth.parse(response.body.output); + } else { + DeliveryV0ResponseSchema.parse(response.body.output); + } + } else if (testPayload.version === 'v1') { + ProxyV1RequestSchema.parse(testPayload.input.request.body); + if (testPayload.scenario === 'Oauth') { + DeliveryV1ResponseSchemaForOauth.parse(response.body.output); + } else { + DeliveryV1ResponseSchema.parse(response.body.output); + } + } + break; + default: + break; + } + return true; +}; From 650911e44c5c99f346f4bcfd8145fcd6993d7759 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Mon, 29 Jan 2024 18:26:32 +0530 Subject: [PATCH 04/37] fix: typo --- .../campaign_manager/dataDelivery/business.ts | 4 ++-- .../campaign_manager/dataDelivery/data.ts | 6 +++--- .../campaign_manager/dataDelivery/oauth.ts | 16 +++++++-------- .../campaign_manager/dataDelivery/other.ts | 20 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts index 9c62f55387..9de1c4b49d 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts @@ -74,7 +74,7 @@ const metadataArray = [proxyMetdata1, proxyMetdata2]; // Test scenarios for the test cases // =================================== -export const testScneariosForV0API = [ +export const testScenariosForV0API = [ { id: 'cm360_v0_scenario_1', name: 'campaign_manager', @@ -281,7 +281,7 @@ export const testScneariosForV0API = [ }, ]; -export const testScneariosForV1API = [ +export const testScenariosForV1API = [ { id: 'cm360_v1_scenario_1', name: 'campaign_manager', diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts index 994ec0a2ee..0373ca9992 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts @@ -1,10 +1,10 @@ -import { testScneariosForV0API, testScneariosForV1API } from './business'; +import { testScenariosForV0API, testScenariosForV1API } from './business'; import { v0oauthScenarios, v1oauthScenarios } from './oauth'; import { otherScenariosV0, otherScenariosV1 } from './other'; export const data = [ - ...testScneariosForV0API, - ...testScneariosForV1API, + ...testScenariosForV0API, + ...testScenariosForV1API, ...v0oauthScenarios, ...v1oauthScenarios, ...otherScenariosV0, diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts index 1b70a9e48f..eaa29f5c37 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts @@ -53,7 +53,7 @@ export const v0oauthScenarios = [ id: 'cm360_v0_oauth_scenario_1', name: 'campaign_manager', description: - '[Proxy v0 API] :: Oauth scneario where valid credentials are missing as mock response from destination', + '[Proxy v0 API] :: Oauth where valid credentials are missing as mock response from destination', successCriteria: 'Since the error from the destination is 401 - the proxy should return 500 with authErrorCategory as REFRESH_TOKEN', scenario: 'Oauth', @@ -128,7 +128,7 @@ export const v0oauthScenarios = [ id: 'cm360_v0_oauth_scenario_2', name: 'campaign_manager', description: - '[Proxy v0 API] :: Oauth scneario where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + '[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 500 with authErrorCategory as AUTH_STATUS_INACTIVE', scenario: 'Oauth', @@ -200,7 +200,7 @@ export const v0oauthScenarios = [ id: 'cm360_v0_oauth_scenario_3', name: 'campaign_manager', description: - '[Proxy v0 API] :: Oauth scneario where google.auth.exceptions.RefreshError invalid_grant error as mock response from destination', + '[Proxy v0 API] :: Oauth where google.auth.exceptions.RefreshError invalid_grant error as mock response from destination', successCriteria: 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', scenario: 'Oauth', @@ -254,7 +254,7 @@ export const v0oauthScenarios = [ id: 'cm360_v0_oauth_scenario_4', name: 'campaign_manager', description: - '[Proxy v0 API] :: Oauth scneario where google.auth.exceptions.RefreshError refresh error as mock response from destination', + '[Proxy v0 API] :: Oauth where google.auth.exceptions.RefreshError refresh error as mock response from destination', successCriteria: 'Should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', scenario: 'Oauth', feature: 'dataDelivery', @@ -307,7 +307,7 @@ export const v1oauthScenarios = [ id: 'cm360_v1_oauth_scenario_1', name: 'campaign_manager', description: - '[Proxy v1 API] :: Oauth scneario where valid credentials are missing as mock response from destination', + '[Proxy v1 API] :: Oauth where valid credentials are missing as mock response from destination', successCriteria: 'Since the error from the destination is 401 - the proxy should return 500 with authErrorCategory as REFRESH_TOKEN', scenario: 'Oauth', @@ -370,7 +370,7 @@ export const v1oauthScenarios = [ id: 'cm360_v1_oauth_scenario_2', name: 'campaign_manager', description: - '[Proxy v1 API] :: Oauth scneario where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + '[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 500 with authErrorCategory as AUTH_STATUS_INACTIVE', scenario: 'Oauth', @@ -433,7 +433,7 @@ export const v1oauthScenarios = [ id: 'cm360_v1_oauth_scenario_3', name: 'campaign_manager', description: - '[Proxy v1 API] :: Oauth scneario where google.auth.exceptions.RefreshError invalid_grant error as mock response from destination', + '[Proxy v1 API] :: Oauth where google.auth.exceptions.RefreshError invalid_grant error as mock response from destination', successCriteria: 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', scenario: 'Oauth', @@ -496,7 +496,7 @@ export const v1oauthScenarios = [ id: 'cm360_v1_oauth_scenario_4', name: 'campaign_manager', description: - '[Proxy v1 API] :: Oauth scneario where google.auth.exceptions.RefreshError refresh error as mock response from destination', + '[Proxy v1 API] :: Oauth where google.auth.exceptions.RefreshError refresh error as mock response from destination', successCriteria: 'Should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', scenario: 'Oauth', feature: 'dataDelivery', diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts index b1b1337680..1be0af62f3 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts @@ -5,7 +5,7 @@ export const otherScenariosV0 = [ id: 'cm360_v0_other_scenario_1', name: 'campaign_manager', description: - '[Proxy v0 API] :: Scneario for testing Service Unavailable error from destination', + '[Proxy v0 API] :: Scenario for testing Service Unavailable error from destination', successCriteria: 'Should return 500 status code with error message', scenario: 'Framework', feature: 'dataDelivery', @@ -55,7 +55,7 @@ export const otherScenariosV0 = [ { id: 'cm360_v0_other_scenario_2', name: 'campaign_manager', - description: '[Proxy v0 API] :: Scneario for testing Internal Server error from destination', + description: '[Proxy v0 API] :: Scenario for testing Internal Server error from destination', successCriteria: 'Should return 500 status code with error message', scenario: 'Framework', feature: 'dataDelivery', @@ -99,7 +99,7 @@ export const otherScenariosV0 = [ { id: 'cm360_v0_other_scenario_3', name: 'campaign_manager', - description: '[Proxy v0 API] :: Scneario for testing Gateway Time Out error from destination', + description: '[Proxy v0 API] :: Scenario for testing Gateway Time Out error from destination', successCriteria: 'Should return 500 status code with error message', scenario: 'Framework', feature: 'dataDelivery', @@ -143,7 +143,7 @@ export const otherScenariosV0 = [ { id: 'cm360_v0_other_scenario_4', name: 'campaign_manager', - description: '[Proxy v0 API] :: Scneario for testing null response from destination', + description: '[Proxy v0 API] :: Scenario for testing null response from destination', successCriteria: 'Should return 500 status code with error message', scenario: 'Framework', feature: 'dataDelivery', @@ -188,7 +188,7 @@ export const otherScenariosV0 = [ id: 'cm360_v0_other_scenario_5', name: 'campaign_manager', description: - '[Proxy v0 API] :: Scneario for testing null and no status response from destination', + '[Proxy v0 API] :: Scenario for testing null and no status response from destination', successCriteria: 'Should return 500 status code with error message', scenario: 'Framework', feature: 'dataDelivery', @@ -236,7 +236,7 @@ export const otherScenariosV1 = [ id: 'cm360_v1_other_scenario_1', name: 'campaign_manager', description: - '[Proxy v1 API] :: Scneario for testing Service Unavailable error from destination', + '[Proxy v1 API] :: Scenario for testing Service Unavailable error from destination', successCriteria: 'Should return 500 status code with error message', scenario: 'Framework', feature: 'dataDelivery', @@ -296,7 +296,7 @@ export const otherScenariosV1 = [ { id: 'cm360_v1_other_scenario_2', name: 'campaign_manager', - description: '[Proxy v1 API] :: Scneario for testing Internal Server error from destination', + 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', @@ -355,7 +355,7 @@ export const otherScenariosV1 = [ { id: 'cm360_v1_other_scenario_3', name: 'campaign_manager', - description: '[Proxy v1 API] :: Scneario for testing Gateway Time Out error from destination', + 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', @@ -414,7 +414,7 @@ export const otherScenariosV1 = [ { id: 'cm360_v1_other_scenario_4', name: 'campaign_manager', - description: '[Proxy v1 API] :: Scneario for testing null response from destination', + description: '[Proxy v1 API] :: Scenario for testing null response from destination', successCriteria: 'Should return 500 status code with error message', scenario: 'Framework', feature: 'dataDelivery', @@ -474,7 +474,7 @@ export const otherScenariosV1 = [ id: 'cm360_v1_other_scenario_5', name: 'campaign_manager', description: - '[Proxy v1 API] :: Scneario for testing null and no status response from destination', + '[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', From 84b6a5d4c054e010710d815a1e09ce9dc37aa493 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Mon, 29 Jan 2024 18:30:11 +0530 Subject: [PATCH 05/37] Update test/integrations/destinations/campaign_manager/dataDelivery/business.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../destinations/campaign_manager/dataDelivery/business.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts index 9de1c4b49d..3b47b62d4a 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts @@ -1,7 +1,7 @@ import { ProxyMetdata } from '../../../../../src/types'; import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils'; -// Boilerplat data for the test cases +// Boilerplate data for the test cases // ====================================== const commonHeaders = { From 686f5246d8b90a45e85d451d1c2ae47b2512a190 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Mon, 29 Jan 2024 18:30:37 +0530 Subject: [PATCH 06/37] Update test/integrations/destinations/campaign_manager/dataDelivery/business.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../destinations/campaign_manager/dataDelivery/business.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts index 3b47b62d4a..6e66650577 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts @@ -79,7 +79,7 @@ export const testScenariosForV0API = [ id: 'cm360_v0_scenario_1', name: 'campaign_manager', description: - '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 without any error', + '[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', From 76e02848c58a6630c36f724dc4ccbac3d29a8007 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Thu, 1 Feb 2024 21:45:17 +0530 Subject: [PATCH 07/37] fix: api contract for v1 proxy --- src/controllers/delivery.ts | 7 ++++++- src/services/destination/postTransformation.ts | 4 +++- .../destinations/braze/dataDelivery/data.ts | 6 ++---- .../campaign_manager/dataDelivery/other.ts | 15 +++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/controllers/delivery.ts b/src/controllers/delivery.ts index e0839a7eda..4334dc33b2 100644 --- a/src/controllers/delivery.ts +++ b/src/controllers/delivery.ts @@ -1,6 +1,7 @@ /* eslint-disable prefer-destructuring */ /* eslint-disable sonarjs/no-duplicate-string */ import { Context } from 'koa'; +import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib'; import { MiscService } from '../services/misc'; import { DeliveryV1Response, @@ -84,7 +85,11 @@ export class DeliveryController { ); } ctx.body = { output: deliveryResponse }; - ControllerUtility.deliveryPostProcess(ctx, deliveryResponse.status); + if (isDefinedAndNotNullAndNotEmpty(deliveryResponse.authErrorCategory)) { + ControllerUtility.deliveryPostProcess(ctx, deliveryResponse.status); + } else { + ControllerUtility.deliveryPostProcess(ctx); + } logger.debug('Native(Delivery):: Response from transformer::', JSON.stringify(ctx.body)); return ctx; diff --git a/src/services/destination/postTransformation.ts b/src/services/destination/postTransformation.ts index 081c40a07c..cc2437fd8e 100644 --- a/src/services/destination/postTransformation.ts +++ b/src/services/destination/postTransformation.ts @@ -186,9 +186,11 @@ export class DestinationPostTransformationService { const resp = { response: responses, statTags: errObj.statTags, - authErrorCategory: errObj.authErrorCategory, message: errObj.message.toString(), status: errObj.status, + ...(errObj.authErrorCategory && { + authErrorCategory: errObj.authErrorCategory, + }), } as DeliveryV1Response; ErrorReportingService.reportError(error, metaTo.errorContext, resp); diff --git a/test/integrations/destinations/braze/dataDelivery/data.ts b/test/integrations/destinations/braze/dataDelivery/data.ts index 8162e75720..3c1a97811e 100644 --- a/test/integrations/destinations/braze/dataDelivery/data.ts +++ b/test/integrations/destinations/braze/dataDelivery/data.ts @@ -629,7 +629,7 @@ export const data = [ }, output: { response: { - status: 401, + status: 200, body: { output: { status: 401, @@ -662,7 +662,6 @@ export const data = [ module: 'destination', workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', }, - authErrorCategory: '', message: 'Request failed for braze with status: 401', }, }, @@ -770,7 +769,7 @@ export const data = [ }, output: { response: { - status: 401, + status: 200, body: { output: { status: 401, @@ -840,7 +839,6 @@ export const data = [ module: 'destination', workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o', }, - authErrorCategory: '', message: 'Request failed for braze with status: 401', }, }, diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts index 1be0af62f3..e280d89959 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts @@ -252,7 +252,7 @@ export const otherScenariosV1 = [ }, output: { response: { - status: 500, + status: 200, body: { output: { response: [ @@ -284,7 +284,6 @@ export const otherScenariosV1 = [ destinationId: 'default-destinationId', workspaceId: 'default-workspaceId', }, - authErrorCategory: '', message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', status: 500, @@ -312,7 +311,7 @@ export const otherScenariosV1 = [ }, output: { response: { - status: 500, + status: 200, body: { output: { response: [ @@ -343,7 +342,6 @@ export const otherScenariosV1 = [ destinationId: 'default-destinationId', workspaceId: 'default-workspaceId', }, - authErrorCategory: '', message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', status: 500, @@ -371,7 +369,7 @@ export const otherScenariosV1 = [ }, output: { response: { - status: 500, + status: 200, body: { output: { response: [ @@ -402,7 +400,6 @@ export const otherScenariosV1 = [ destinationId: 'default-destinationId', workspaceId: 'default-workspaceId', }, - authErrorCategory: '', message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', status: 500, @@ -430,7 +427,7 @@ export const otherScenariosV1 = [ }, output: { response: { - status: 500, + status: 200, body: { output: { response: [ @@ -461,7 +458,6 @@ export const otherScenariosV1 = [ destinationId: 'default-destinationId', workspaceId: 'default-workspaceId', }, - authErrorCategory: '', message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', status: 500, @@ -490,7 +486,7 @@ export const otherScenariosV1 = [ }, output: { response: { - status: 500, + status: 200, body: { output: { response: [ @@ -521,7 +517,6 @@ export const otherScenariosV1 = [ destinationId: 'default-destinationId', workspaceId: 'default-workspaceId', }, - authErrorCategory: '', message: 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation', status: 500, From d2e65f4d936ce9ccbe1308cec0548d1bbde7fea1 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Mon, 5 Feb 2024 12:04:03 +0530 Subject: [PATCH 08/37] chore: clean up zod type --- src/types/zodTypes.ts | 112 +++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 73 deletions(-) diff --git a/src/types/zodTypes.ts b/src/types/zodTypes.ts index f3b9c57dd4..6c7288822b 100644 --- a/src/types/zodTypes.ts +++ b/src/types/zodTypes.ts @@ -56,6 +56,20 @@ export const ProxyV1RequestSchema = z.object({ destinationConfig: z.record(z.unknown()), }); +const validateStatTags = (data: any) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.statTags); + } + return true; +}; + +const validateAuthErrorCategory = (data: any) => { + if (!isHttpStatusSuccess(data.status)) { + return isDefinedAndNotNullAndNotEmpty(data.authErrorCategory); + } + return true; +}; + export const DeliveryV0ResponseSchema = z .object({ status: z.number(), @@ -64,19 +78,11 @@ export const DeliveryV0ResponseSchema = z statTags: z.record(z.unknown()).optional(), authErrorCategory: z.string().optional(), }) - .refine( - (data) => { - if (!isHttpStatusSuccess(data.status)) { - return isDefinedAndNotNullAndNotEmpty(data.statTags); - } - return true; - }, - { - // eslint-disable-next-line sonarjs/no-duplicate-string - message: "statTags can't be empty when status is not a 2XX", - path: ['statTags'], // Pointing out which field is invalid - }, - ); + .refine(validateStatTags, { + // eslint-disable-next-line sonarjs/no-duplicate-string + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }); export const DeliveryV0ResponseSchemaForOauth = z .object({ @@ -86,30 +92,14 @@ export const DeliveryV0ResponseSchemaForOauth = z statTags: z.record(z.unknown()).optional(), authErrorCategory: z.string().optional(), }) - .refine( - (data) => { - if (!isHttpStatusSuccess(data.status)) { - return isDefinedAndNotNullAndNotEmpty(data.statTags); - } - return true; - }, - { - message: "statTags can't be empty when status is not a 2XX", - path: ['statTags'], // Pointing out which field is invalid - }, - ) - .refine( - (data) => { - if (!isHttpStatusSuccess(data.status)) { - return isDefinedAndNotNullAndNotEmpty(data.authErrorCategory); - } - return true; - }, - { - message: "authErrorCategory can't be empty when status is not a 2XX", - path: ['authErrorCategory'], // Pointing out which field is invalid - }, - ); + .refine(validateStatTags, { + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }) + .refine(validateAuthErrorCategory, { + message: "authErrorCategory can't be empty when status is not a 2XX", + path: ['authErrorCategory'], // Pointing out which field is invalid + }); const DeliveryJobStateSchema = z.object({ error: z.string(), @@ -125,18 +115,10 @@ export const DeliveryV1ResponseSchema = z authErrorCategory: z.string().optional(), response: z.array(DeliveryJobStateSchema), }) - .refine( - (data) => { - if (!isHttpStatusSuccess(data.status)) { - return isDefinedAndNotNullAndNotEmpty(data.statTags); - } - return true; - }, - { - message: "statTags can't be empty when status is not a 2XX", - path: ['statTags'], // Pointing out which field is invalid - }, - ); + .refine(validateStatTags, { + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }); export const DeliveryV1ResponseSchemaForOauth = z .object({ @@ -146,27 +128,11 @@ export const DeliveryV1ResponseSchemaForOauth = z authErrorCategory: z.string().optional(), response: z.array(DeliveryJobStateSchema), }) - .refine( - (data) => { - if (!isHttpStatusSuccess(data.status)) { - return isDefinedAndNotNullAndNotEmpty(data.statTags); - } - return true; - }, - { - message: "statTags can't be empty when status is not a 2XX", - path: ['statTags'], // Pointing out which field is invalid - }, - ) - .refine( - (data) => { - if (!isHttpStatusSuccess(data.status)) { - return isDefinedAndNotNullAndNotEmpty(data.authErrorCategory); - } - return true; - }, - { - message: "authErrorCategory can't be empty when status is not a 2XX", - path: ['authErrorCategory'], // Pointing out which field is invalid - }, - ); + .refine(validateStatTags, { + message: "statTags can't be empty when status is not a 2XX", + path: ['statTags'], // Pointing out which field is invalid + }) + .refine(validateAuthErrorCategory, { + message: "authErrorCategory can't be empty when status is not a 2XX", + path: ['authErrorCategory'], // Pointing out which field is invalid + }); From 7ce0a6605ce9a65d29f69d4be5f1c54f382ab12c Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Mon, 5 Feb 2024 12:12:27 +0530 Subject: [PATCH 09/37] chore: update testutils --- test/integrations/testUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index a761170afc..c39dd33be8 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -42,7 +42,9 @@ export const getAllTestMockDataFilePaths = (dirPath: string, destination: string const globPattern = join(dirPath, '**', 'network.ts'); let testFilePaths = globSync(globPattern); if (destination) { - const commonTestFilePaths = testFilePaths.filter((testFile) => testFile.includes('common')); + const commonTestFilePaths = testFilePaths.filter((testFile) => + testFile.includes('test/integrations/common'), + ); testFilePaths = testFilePaths.filter((testFile) => testFile.includes(destination)); testFilePaths = [...commonTestFilePaths, ...testFilePaths]; } From 99f5cb27328cb3595ffbd7f3a6a9f26ba112ca17 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Mon, 5 Feb 2024 22:21:06 +0530 Subject: [PATCH 10/37] chore: update V0 proxy request type and zod schema --- src/types/index.ts | 1 + src/types/zodTypes.ts | 1 + test/integrations/testUtils.ts | 7 ++++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/types/index.ts b/src/types/index.ts index df8d3a9182..1a0160d2f2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -34,6 +34,7 @@ type ProxyV0Request = { }; files?: Record; metadata: ProxyMetdata; + destinationConfig: Record; }; type ProxyV1Request = { diff --git a/src/types/zodTypes.ts b/src/types/zodTypes.ts index 6c7288822b..2e60e60b12 100644 --- a/src/types/zodTypes.ts +++ b/src/types/zodTypes.ts @@ -33,6 +33,7 @@ export const ProxyV0RequestSchema = z.object({ .optional(), files: z.record(z.unknown()).optional(), metadata: ProxyMetadataSchema, + destinationConfig: z.record(z.unknown()), }); export const ProxyV1RequestSchema = z.object({ diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index c39dd33be8..a6675742bc 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -379,7 +379,11 @@ export const compareObjects = (obj1, obj2, logPrefix = '', differences: string[] return differences; }; -export const generateProxyV0Payload = (payloadParameters: any, metadataInput?: ProxyMetdata) => { +export const generateProxyV0Payload = ( + payloadParameters: any, + metadataInput?: ProxyMetdata, + destinationConfig?: any, +) => { let metadata: ProxyMetdata = { jobId: 1, attemptNum: 1, @@ -411,6 +415,7 @@ export const generateProxyV0Payload = (payloadParameters: any, metadataInput?: P }, files: payloadParameters.files || {}, metadata, + destinationConfig: destinationConfig || {}, }; return removeUndefinedAndNullValues(payload); }; From 325433b9188c8d1dbe740c7e193cdc2e58fdd751 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Thu, 8 Feb 2024 12:45:53 +0530 Subject: [PATCH 11/37] feat: adding zod validations (#3066) * feat: add type definitions for test cases * fix: update networkHandler for rakuten --------- Co-authored-by: Utsab Chowdhury --- .../destination/postTransformation.ts | 8 +- src/types/index.ts | 12 +- src/types/zodTypes.ts | 105 ++++- src/v0/destinations/rakuten/networkHandler.js | 5 +- .../rakuten/networkHandler.test.js | 11 +- .../klaviyo/processor/ecomTestData.ts | 25 +- .../klaviyo/processor/groupTestData.ts | 29 +- .../klaviyo/processor/identifyTestData.ts | 50 ++- .../klaviyo/processor/screenTestData.ts | 25 +- .../klaviyo/processor/trackTestData.ts | 28 +- .../klaviyo/processor/validationTestData.ts | 35 +- .../destinations/klaviyo/router/data.ts | 381 +++++++++--------- test/integrations/destinations/mp/common.ts | 6 +- .../destinations/mp/router/data.ts | 10 + .../destinations/the_trade_desk/common.ts | 19 +- test/integrations/testTypes.ts | 53 +++ test/integrations/testUtils.ts | 51 ++- 17 files changed, 604 insertions(+), 249 deletions(-) diff --git a/src/services/destination/postTransformation.ts b/src/services/destination/postTransformation.ts index cc2437fd8e..161547683b 100644 --- a/src/services/destination/postTransformation.ts +++ b/src/services/destination/postTransformation.ts @@ -75,7 +75,13 @@ export class DestinationPostTransformationService { ): RouterTransformationResponse[] { const resultantPayloads: RouterTransformationResponse[] = cloneDeep(transformedPayloads); resultantPayloads.forEach((resultantPayload) => { - if (resultantPayload.batchedRequest && resultantPayload.batchedRequest.userId) { + if (Array.isArray(resultantPayload.batchedRequest)) { + resultantPayload.batchedRequest.forEach((batchedRequest) => { + if (batchedRequest.userId) { + batchedRequest.userId = `${batchedRequest.userId}`; + } + }); + } else if (resultantPayload.batchedRequest && resultantPayload.batchedRequest.userId) { resultantPayload.batchedRequest.userId = `${resultantPayload.batchedRequest.userId}`; } }); diff --git a/src/types/index.ts b/src/types/index.ts index 1a0160d2f2..b81071476d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -6,7 +6,7 @@ type ProcessorTransformationOutput = { type: string; method: string; endpoint: string; - userId: string; + userId?: string; headers?: Record; params?: Record; body?: { @@ -142,7 +142,7 @@ type ProcessorTransformationRequest = { message: object; metadata: Metadata; destination: Destination; - libraries: UserTransformationLibrary[]; + libraries?: UserTransformationLibrary[]; }; type RouterTransformationRequestData = { @@ -162,17 +162,17 @@ type ProcessorTransformationResponse = { metadata: Metadata; statusCode: number; error?: string; - statTags: object; + statTags?: object; }; type RouterTransformationResponse = { - batchedRequest?: ProcessorTransformationOutput; + batchedRequest?: ProcessorTransformationOutput | ProcessorTransformationOutput[]; metadata: Metadata[]; destination: Destination; batched: boolean; statusCode: number; - error: string; - statTags: object; + error?: string; + statTags?: object; }; type SourceTransformationOutput = { diff --git a/src/types/zodTypes.ts b/src/types/zodTypes.ts index 2e60e60b12..0a65a2bae2 100644 --- a/src/types/zodTypes.ts +++ b/src/types/zodTypes.ts @@ -2,6 +2,109 @@ import { z } from 'zod'; import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib'; import { isHttpStatusSuccess } from '../v0/util'; +const ProcessorTransformationOutputSchema = z.object({ + version: z.string(), + type: z.string(), + method: z.string(), + endpoint: z.string(), + userId: z.string().optional(), + headers: z.record(z.unknown()).optional(), + params: z.record(z.unknown()).optional(), + body: z + .object({ + JSON: z.record(z.unknown()).optional(), + JSON_ARRAY: z.record(z.unknown()).optional(), + XML: z.record(z.unknown()).optional(), + FORM: z.record(z.unknown()).optional(), + }) + .optional(), + files: z.record(z.unknown()).optional(), +}); + +export const ProcessorTransformationResponseSchema = z + .object({ + output: ProcessorTransformationOutputSchema.optional(), + metadata: z.record(z.unknown()), + statusCode: z.number(), + error: z.string().optional(), + statTags: z.record(z.unknown()).optional(), + }) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.statusCode)) { + return ( + isDefinedAndNotNullAndNotEmpty(data.statTags) || + isDefinedAndNotNullAndNotEmpty(data.error) + ); + } + return true; + }, + { + message: "statTags and error can't be empty when status is not a 2XX", + path: ['statTags', 'error'], // Pointing out which field is invalid + }, + ) + .refine( + (data) => { + if (isHttpStatusSuccess(data.statusCode)) { + return isDefinedAndNotNullAndNotEmpty(data.output); + } + return true; + }, + { + message: "output can't be empty when status is 2XX", + path: ['output'], // Pointing out which field is invalid + }, + ); + +export const ProcessorTransformationResponseListSchema = z.array( + ProcessorTransformationResponseSchema, +); + +export const RouterTransformationResponseSchema = z + .object({ + batchedRequest: z + .array(ProcessorTransformationOutputSchema) + .or(ProcessorTransformationOutputSchema) + .optional(), + metadata: z.array(z.record(z.unknown())), // array of metadata + destination: z.record(z.unknown()), + batched: z.boolean(), + statusCode: z.number(), + error: z.string().optional(), + statTags: z.record(z.unknown()).optional(), + }) + .refine( + (data) => { + if (!isHttpStatusSuccess(data.statusCode)) { + return ( + isDefinedAndNotNullAndNotEmpty(data.statTags) || + isDefinedAndNotNullAndNotEmpty(data.error) + ); + } + return true; + }, + { + message: "statTags and error can't be empty when status is not a 2XX", + path: ['statTags', 'error'], // Pointing out which field is invalid + }, + ) + .refine( + (data) => { + if (isHttpStatusSuccess(data.statusCode)) { + return isDefinedAndNotNullAndNotEmpty(data.batchedRequest); + } + return true; + }, + { + message: "batchedRequest can't be empty when status is 2XX", + path: ['batchedRequest'], // Pointing out which field is invalid + }, + ); + +export const RouterTransformationResponseListSchema = z.array(RouterTransformationResponseSchema); + +// Proxy related schemas export const ProxyMetadataSchema = z.object({ jobId: z.number(), attemptNum: z.number(), @@ -10,7 +113,7 @@ export const ProxyMetadataSchema = z.object({ destinationId: z.string(), workspaceId: z.string(), secret: z.record(z.unknown()), - destInfo: z.object({}).optional(), + destInfo: z.record(z.unknown()).optional(), omitempty: z.record(z.unknown()).optional(), dontBatch: z.boolean(), }); diff --git a/src/v0/destinations/rakuten/networkHandler.js b/src/v0/destinations/rakuten/networkHandler.js index 1b16bd5538..6c89d83947 100644 --- a/src/v0/destinations/rakuten/networkHandler.js +++ b/src/v0/destinations/rakuten/networkHandler.js @@ -27,7 +27,8 @@ const extractContent = (xmlPayload, tagName) => { return match ? match[1] : null; }; -const responseHandler = (destinationResponse) => { +const responseHandler = (responseParams) => { + const { destinationResponse } = responseParams; const msg = `[${DESTINATION} Response Handler] - Request Processed Successfully`; const { response, status } = destinationResponse; if (status === 400) { @@ -99,5 +100,5 @@ class networkHandler { module.exports = { networkHandler, - responseHandler + responseHandler, }; diff --git a/src/v0/destinations/rakuten/networkHandler.test.js b/src/v0/destinations/rakuten/networkHandler.test.js index 70461c86c1..da74e05cb3 100644 --- a/src/v0/destinations/rakuten/networkHandler.test.js +++ b/src/v0/destinations/rakuten/networkHandler.test.js @@ -8,7 +8,7 @@ describe('responseHandler', () => { status: 200, }; - const result = responseHandler(destinationResponse); + const result = responseHandler({ destinationResponse }); expect(result.status).toBe(200); expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully'); @@ -21,7 +21,7 @@ describe('responseHandler', () => { status: 400, }; expect(() => { - responseHandler(destinationResponse); + responseHandler({ destinationResponse }); }).toThrow('Request failed with status: 400 due to invalid Marketing Id'); }); @@ -31,7 +31,7 @@ describe('responseHandler', () => { status: 200, }; expect(() => { - responseHandler(destinationResponse); + responseHandler({ destinationResponse }); }).toThrow( 'Request failed with status: 200 due to Access denied. Can you try to enable pixel tracking for this mid.', ); @@ -43,7 +43,7 @@ describe('responseHandler', () => { status: 200, }; - const result = responseHandler(destinationResponse); + const result = responseHandler({ destinationResponse }); expect(result.status).toBe(200); expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully'); @@ -57,8 +57,7 @@ describe('responseHandler', () => { }; expect(() => { - responseHandler(destinationResponse); + responseHandler({ destinationResponse }); }).toThrow('Request failed with status: 200 with number of bad records 1'); - }); }); diff --git a/test/integrations/destinations/klaviyo/processor/ecomTestData.ts b/test/integrations/destinations/klaviyo/processor/ecomTestData.ts index fab4cf85ce..34eff45232 100644 --- a/test/integrations/destinations/klaviyo/processor/ecomTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/ecomTestData.ts @@ -1,10 +1,23 @@ -import { overrideDestination, transformResultBuilder } from '../../../testUtils'; +import { overrideDestination, transformResultBuilder, generateMetadata } from '../../../testUtils'; +import { ProcessorTestData } from '../../../testTypes'; +import { Destination } from '../../../../../src/types'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; const commonTraits = { @@ -26,7 +39,7 @@ const commonOutputHeaders = { revision: '2023-02-22', }; -export const ecomTestData = [ +export const ecomTestData: ProcessorTestData[] = [ { id: 'klaviyo-ecom-test-1', name: 'klaviyo', @@ -64,6 +77,7 @@ export const ecomTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }, + metadata: generateMetadata(1), }, ], }, @@ -108,6 +122,7 @@ export const ecomTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, @@ -170,6 +185,7 @@ export const ecomTestData = [ }, anonymousId: '9c6bd77ea9da3e68', }, + metadata: generateMetadata(2), }, ], }, @@ -220,6 +236,7 @@ export const ecomTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(2), }, ], }, @@ -280,6 +297,7 @@ export const ecomTestData = [ }, originalTimestamp: '2021-01-25T15:32:56.409Z', }, + metadata: generateMetadata(3), }, ], }, @@ -336,6 +354,7 @@ export const ecomTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(3), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/groupTestData.ts b/test/integrations/destinations/klaviyo/processor/groupTestData.ts index 031c949c4b..0002f7ce90 100644 --- a/test/integrations/destinations/klaviyo/processor/groupTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/groupTestData.ts @@ -1,10 +1,27 @@ -import { generateSimplifiedGroupPayload, transformResultBuilder } from '../../../testUtils'; +import { Destination } from '../../../../../src/types'; +import { ProcessorTestData } from '../../../testTypes'; +import { + generateMetadata, + generateSimplifiedGroupPayload, + transformResultBuilder, +} from '../../../testUtils'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; const headers = { @@ -16,7 +33,7 @@ const headers = { const commonEndpoint = 'https://a.klaviyo.com/api/profile-subscription-bulk-create-jobs'; -export const groupTestData = [ +export const groupTestData: ProcessorTestData[] = [ { id: 'klaviyo-group-test-1', name: 'klaviyo', @@ -47,6 +64,7 @@ export const groupTestData = [ }, timestamp: '2020-01-21T00:21:34.208Z', }), + metadata: generateMetadata(1), }, ], }, @@ -74,6 +92,7 @@ export const groupTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, @@ -109,6 +128,7 @@ export const groupTestData = [ }, timestamp: '2020-01-21T00:21:34.208Z', }), + metadata: generateMetadata(2), }, ], }, @@ -126,8 +146,11 @@ export const groupTestData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 400, + metadata: generateMetadata(2), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/identifyTestData.ts b/test/integrations/destinations/klaviyo/processor/identifyTestData.ts index 8b5503fad9..f632cb767c 100644 --- a/test/integrations/destinations/klaviyo/processor/identifyTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/identifyTestData.ts @@ -3,13 +3,27 @@ import { overrideDestination, transformResultBuilder, generateSimplifiedIdentifyPayload, + generateMetadata, } from '../../../testUtils'; +import { ProcessorTestData } from '../../../testTypes'; +import { Destination } from '../../../../../src/types'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; const commonTraits = { @@ -81,7 +95,7 @@ const originalTimestamp = '2021-01-03T17:02:53.193Z'; const commonUserUpdateEndpoint = 'https://a.klaviyo.com/api/profiles/01GW3PHVY0MTCDGS0A1612HARX'; const subscribeEndpoint = 'https://a.klaviyo.com/api/profile-subscription-bulk-create-jobs'; -export const identifyData = [ +export const identifyData: ProcessorTestData[] = [ { id: 'klaviyo-identify-test-1', name: 'klaviyo', @@ -108,6 +122,7 @@ export const identifyData = [ userId, sentAt, }), + metadata: generateMetadata(1), }, ], }, @@ -131,6 +146,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(1), }, { output: transformResultBuilder({ @@ -146,6 +162,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, @@ -184,6 +201,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(2), }, ], }, @@ -215,6 +233,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(2), }, { output: transformResultBuilder({ @@ -230,6 +249,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(2), }, ], }, @@ -249,12 +269,10 @@ export const identifyData = [ request: { body: [ { - destination: { - Config: { - publicApiKey: 'dummyPublicApiKey', - privateApiKey: 'dummyPrivateApiKeyforfailure', - }, - }, + destination: overrideDestination(destination, { + publicApiKey: 'dummyPublicApiKey', + privateApiKey: 'dummyPrivateApiKeyforfailure', + }), message: generateSimplifiedIdentifyPayload({ sentAt, userId, @@ -267,6 +285,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(3), }, ], }, @@ -285,8 +304,11 @@ export const identifyData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 500, + metadata: generateMetadata(3), }, ], }, @@ -319,6 +341,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(4), }, ], }, @@ -342,6 +365,7 @@ export const identifyData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(4), }, ], }, @@ -371,6 +395,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(5), }, ], }, @@ -402,6 +427,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(5), }, { output: transformResultBuilder({ @@ -417,6 +443,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(5), }, ], }, @@ -450,6 +477,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(6), }, ], }, @@ -476,6 +504,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(6), }, { output: transformResultBuilder({ @@ -491,6 +520,7 @@ export const identifyData = [ }, }), statusCode: 200, + metadata: generateMetadata(6), }, ], }, @@ -524,6 +554,7 @@ export const identifyData = [ anonymousId, originalTimestamp, }), + metadata: generateMetadata(7), }, ], }, @@ -541,8 +572,11 @@ export const identifyData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 400, + metadata: generateMetadata(7), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/screenTestData.ts b/test/integrations/destinations/klaviyo/processor/screenTestData.ts index 3779747a4e..0a20110236 100644 --- a/test/integrations/destinations/klaviyo/processor/screenTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/screenTestData.ts @@ -1,13 +1,30 @@ -import { generateSimplifiedPageOrScreenPayload, transformResultBuilder } from '../../../testUtils'; +import { Destination } from '../../../../../src/types'; +import { ProcessorTestData } from '../../../testTypes'; +import { + generateMetadata, + generateSimplifiedPageOrScreenPayload, + transformResultBuilder, +} from '../../../testUtils'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; -export const screenTestData = [ +export const screenTestData: ProcessorTestData[] = [ { id: 'klaviyo-screen-test-1', name: 'klaviyo', @@ -47,6 +64,7 @@ export const screenTestData = [ }, 'screen', ), + metadata: generateMetadata(1), }, ], }, @@ -89,6 +107,7 @@ export const screenTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/trackTestData.ts b/test/integrations/destinations/klaviyo/processor/trackTestData.ts index f3bbfb96b9..3bc2b1747a 100644 --- a/test/integrations/destinations/klaviyo/processor/trackTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/trackTestData.ts @@ -1,15 +1,29 @@ +import { Destination } from '../../../../../src/types'; +import { ProcessorTestData } from '../../../testTypes'; import { + generateMetadata, generateSimplifiedTrackPayload, generateTrackPayload, overrideDestination, transformResultBuilder, } from '../../../testUtils'; -const destination = { +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey', }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], }; const commonTraits = { @@ -33,7 +47,7 @@ const commonOutputHeaders = { const eventEndPoint = 'https://a.klaviyo.com/api/events'; -export const trackTestData = [ +export const trackTestData: ProcessorTestData[] = [ { id: 'klaviyo-track-test-1', name: 'klaviyo', @@ -71,6 +85,7 @@ export const trackTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }), + metadata: generateMetadata(1), }, ], }, @@ -110,6 +125,7 @@ export const trackTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(1), }, ], }, @@ -151,6 +167,7 @@ export const trackTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }), + metadata: generateMetadata(2), }, ], }, @@ -187,6 +204,7 @@ export const trackTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(2), }, ], }, @@ -223,6 +241,7 @@ export const trackTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }), + metadata: generateMetadata(3), }, ], }, @@ -256,6 +275,7 @@ export const trackTestData = [ userId: '', }), statusCode: 200, + metadata: generateMetadata(3), }, ], }, @@ -289,6 +309,7 @@ export const trackTestData = [ anonymousId: '9c6bd77ea9da3e68', originalTimestamp: '2021-01-25T15:32:56.409Z', }), + metadata: generateMetadata(4), }, ], }, @@ -306,8 +327,11 @@ export const trackTestData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 400, + metadata: generateMetadata(4), }, ], }, diff --git a/test/integrations/destinations/klaviyo/processor/validationTestData.ts b/test/integrations/destinations/klaviyo/processor/validationTestData.ts index 59556cfe5f..801e03d541 100644 --- a/test/integrations/destinations/klaviyo/processor/validationTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/validationTestData.ts @@ -1,4 +1,26 @@ -export const validationTestData = [ +import { Destination } from '../../../../../src/types'; +import { ProcessorTestData } from '../../../testTypes'; +import { generateMetadata } from '../../../testUtils'; + +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, + Config: { + publicApiKey: 'dummyPublicApiKey', + privateApiKey: 'dummyPrivateApiKey', + }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], +}; + +export const validationTestData: ProcessorTestData[] = [ { id: 'klaviyo-validation-test-1', name: 'klaviyo', @@ -13,12 +35,7 @@ export const validationTestData = [ request: { body: [ { - destination: { - Config: { - publicApiKey: 'dummyPublicApiKey', - privateApiKey: 'dummyPrivateApiKey', - }, - }, + destination, message: { userId: 'user123', type: 'random', @@ -35,6 +52,7 @@ export const validationTestData = [ }, timestamp: '2020-01-21T00:21:34.208Z', }, + metadata: generateMetadata(1), }, ], }, @@ -52,8 +70,11 @@ export const validationTestData = [ feature: 'processor', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, statusCode: 400, + metadata: generateMetadata(1), }, ], }, diff --git a/test/integrations/destinations/klaviyo/router/data.ts b/test/integrations/destinations/klaviyo/router/data.ts index 818089a722..8866a8a546 100644 --- a/test/integrations/destinations/klaviyo/router/data.ts +++ b/test/integrations/destinations/klaviyo/router/data.ts @@ -1,4 +1,184 @@ -export const data = [ +import { Destination, RouterTransformationRequest } from '../../../../../src/types'; +import { RouterTestData } from '../../../testTypes'; +import { generateMetadata } from '../../../testUtils'; + +const destination: Destination = { + ID: '123', + Name: 'klaviyo', + DestinationDefinition: { + ID: '123', + Name: 'klaviyo', + DisplayName: 'klaviyo', + Config: {}, + }, + Config: { + publicApiKey: 'dummyPublicApiKey', + privateApiKey: 'dummyPrivateApiKey', + }, + Enabled: true, + WorkspaceID: '123', + Transformations: [], +}; + +const routerRequest: RouterTransformationRequest = { + input: [ + { + destination, + metadata: generateMetadata(1), + message: { + type: 'identify', + sentAt: '2021-01-03T17:02:53.195Z', + userId: 'test', + channel: 'web', + context: { + os: { name: '', version: '' }, + app: { + name: 'RudderLabs JavaScript SDK', + build: '1.0.0', + version: '1.1.11', + namespace: 'com.rudderlabs.javascript', + }, + traits: { + firstName: 'Test', + lastName: 'Rudderlabs', + email: 'test@rudderstack.com', + phone: '+12 345 578 900', + userId: 'Testc', + title: 'Developer', + organization: 'Rudder', + city: 'Tokyo', + region: 'Kanto', + country: 'JP', + zip: '100-0001', + Flagged: false, + Residence: 'Shibuya', + properties: { consent: ['email', 'sms'] }, + }, + locale: 'en-US', + screen: { density: 2 }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.1.11' }, + campaign: {}, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', + }, + rudderId: '8f8fa6b5-8e24-489c-8e22-61f23f2e364f', + messageId: '2116ef8c-efc3-4ca4-851b-02ee60dad6ff', + anonymousId: '97c46c81-3140-456d-b2a9-690d70aaca35', + integrations: { All: true }, + originalTimestamp: '2021-01-03T17:02:53.193Z', + }, + }, + { + destination, + metadata: generateMetadata(2), + message: { + type: 'identify', + sentAt: '2021-01-03T17:02:53.195Z', + userId: 'test', + channel: 'web', + context: { + os: { name: '', version: '' }, + app: { + name: 'RudderLabs JavaScript SDK', + build: '1.0.0', + version: '1.1.11', + namespace: 'com.rudderlabs.javascript', + }, + traits: { + firstName: 'Test', + lastName: 'Rudderlabs', + email: 'test@rudderstack.com', + phone: '+12 345 578 900', + userId: 'test', + title: 'Developer', + organization: 'Rudder', + city: 'Tokyo', + region: 'Kanto', + country: 'JP', + zip: '100-0001', + Flagged: false, + Residence: 'Shibuya', + properties: { listId: 'XUepkK', subscribe: true, consent: ['email', 'sms'] }, + }, + locale: 'en-US', + screen: { density: 2 }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.1.11' }, + campaign: {}, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', + }, + rudderId: '8f8fa6b5-8e24-489c-8e22-61f23f2e364f', + messageId: '2116ef8c-efc3-4ca4-851b-02ee60dad6ff', + anonymousId: '97c46c81-3140-456d-b2a9-690d70aaca35', + integrations: { All: true }, + originalTimestamp: '2021-01-03T17:02:53.193Z', + }, + }, + { + destination, + metadata: generateMetadata(3), + message: { + userId: 'user123', + type: 'group', + groupId: 'XUepkK', + traits: { subscribe: true }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: ['email'], + }, + ip: '14.5.67.21', + library: { name: 'http' }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + { + destination, + metadata: generateMetadata(4), + message: { + userId: 'user123', + type: 'random', + groupId: 'XUepkK', + traits: { subscribe: true }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: 'email', + }, + ip: '14.5.67.21', + library: { name: 'http' }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + { + destination, + metadata: generateMetadata(5), + message: { + userId: 'user123', + type: 'group', + groupId: '', + traits: { subscribe: true }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: 'email', + }, + ip: '14.5.67.21', + library: { name: 'http' }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + ], + destType: 'klaviyo', +}; + +export const data: RouterTestData[] = [ { id: 'klaviyo-router-test-1', name: 'klaviyo', @@ -10,173 +190,7 @@ export const data = [ version: 'v0', input: { request: { - body: { - input: [ - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 1, userId: 'u1' }, - message: { - type: 'identify', - sentAt: '2021-01-03T17:02:53.195Z', - userId: 'test', - channel: 'web', - context: { - os: { name: '', version: '' }, - app: { - name: 'RudderLabs JavaScript SDK', - build: '1.0.0', - version: '1.1.11', - namespace: 'com.rudderlabs.javascript', - }, - traits: { - firstName: 'Test', - lastName: 'Rudderlabs', - email: 'test@rudderstack.com', - phone: '+12 345 578 900', - userId: 'Testc', - title: 'Developer', - organization: 'Rudder', - city: 'Tokyo', - region: 'Kanto', - country: 'JP', - zip: '100-0001', - Flagged: false, - Residence: 'Shibuya', - properties: { consent: ['email', 'sms'] }, - }, - locale: 'en-US', - screen: { density: 2 }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.1.11' }, - campaign: {}, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', - }, - rudderId: '8f8fa6b5-8e24-489c-8e22-61f23f2e364f', - messageId: '2116ef8c-efc3-4ca4-851b-02ee60dad6ff', - anonymousId: '97c46c81-3140-456d-b2a9-690d70aaca35', - integrations: { All: true }, - originalTimestamp: '2021-01-03T17:02:53.193Z', - }, - }, - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 2, userId: 'u1' }, - message: { - type: 'identify', - sentAt: '2021-01-03T17:02:53.195Z', - userId: 'test', - channel: 'web', - context: { - os: { name: '', version: '' }, - app: { - name: 'RudderLabs JavaScript SDK', - build: '1.0.0', - version: '1.1.11', - namespace: 'com.rudderlabs.javascript', - }, - traits: { - firstName: 'Test', - lastName: 'Rudderlabs', - email: 'test@rudderstack.com', - phone: '+12 345 578 900', - userId: 'test', - title: 'Developer', - organization: 'Rudder', - city: 'Tokyo', - region: 'Kanto', - country: 'JP', - zip: '100-0001', - Flagged: false, - Residence: 'Shibuya', - properties: { listId: 'XUepkK', subscribe: true, consent: ['email', 'sms'] }, - }, - locale: 'en-US', - screen: { density: 2 }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.1.11' }, - campaign: {}, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0', - }, - rudderId: '8f8fa6b5-8e24-489c-8e22-61f23f2e364f', - messageId: '2116ef8c-efc3-4ca4-851b-02ee60dad6ff', - anonymousId: '97c46c81-3140-456d-b2a9-690d70aaca35', - integrations: { All: true }, - originalTimestamp: '2021-01-03T17:02:53.193Z', - }, - }, - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 3, userId: 'u1' }, - message: { - userId: 'user123', - type: 'group', - groupId: 'XUepkK', - traits: { subscribe: true }, - context: { - traits: { - email: 'test@rudderstack.com', - phone: '+12 345 678 900', - consent: ['email'], - }, - ip: '14.5.67.21', - library: { name: 'http' }, - }, - timestamp: '2020-01-21T00:21:34.208Z', - }, - }, - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 4, userId: 'u1' }, - message: { - userId: 'user123', - type: 'random', - groupId: 'XUepkK', - traits: { subscribe: true }, - context: { - traits: { - email: 'test@rudderstack.com', - phone: '+12 345 678 900', - consent: 'email', - }, - ip: '14.5.67.21', - library: { name: 'http' }, - }, - timestamp: '2020-01-21T00:21:34.208Z', - }, - }, - { - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, - metadata: { jobId: 5, userId: 'u1' }, - message: { - userId: 'user123', - type: 'group', - groupId: '', - traits: { subscribe: true }, - context: { - traits: { - email: 'test@rudderstack.com', - phone: '+12 345 678 900', - consent: 'email', - }, - ip: '14.5.67.21', - library: { name: 'http' }, - }, - timestamp: '2020-01-21T00:21:34.208Z', - }, - }, - ], - destType: 'klaviyo', - }, + body: routerRequest, }, }, output: { @@ -263,15 +277,10 @@ export const data = [ files: {}, }, ], - metadata: [ - { jobId: 3, userId: 'u1' }, - { jobId: 2, userId: 'u1' }, - ], + metadata: [generateMetadata(3), generateMetadata(2)], batched: true, statusCode: 200, - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, + destination, }, { batchedRequest: { @@ -315,15 +324,13 @@ export const data = [ }, files: {}, }, - metadata: [{ jobId: 1, userId: 'u1' }], + metadata: [generateMetadata(1)], batched: false, statusCode: 200, - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, + destination, }, { - metadata: [{ jobId: 4, userId: 'u1' }], + metadata: [generateMetadata(4)], batched: false, statusCode: 400, error: 'Event type random is not supported', @@ -334,13 +341,13 @@ export const data = [ feature: 'router', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, + destination, }, { - metadata: [{ jobId: 5, userId: 'u1' }], + metadata: [generateMetadata(5)], batched: false, statusCode: 400, error: 'groupId is a required field for group events', @@ -351,10 +358,10 @@ export const data = [ feature: 'router', implementation: 'native', module: 'destination', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', }, - destination: { - Config: { publicApiKey: 'dummyPublicApiKey', privateApiKey: 'dummyPrivateApiKey' }, - }, + destination, }, ], }, diff --git a/test/integrations/destinations/mp/common.ts b/test/integrations/destinations/mp/common.ts index 76ed25a760..82f0e3202b 100644 --- a/test/integrations/destinations/mp/common.ts +++ b/test/integrations/destinations/mp/common.ts @@ -1,8 +1,10 @@ +import { Destination } from '../../../../src/types'; + const defaultMockFns = () => { jest.spyOn(Date, 'now').mockImplementation(() => new Date(Date.UTC(2020, 0, 25)).valueOf()); }; -const sampleDestination = { +const sampleDestination: Destination = { Config: { apiKey: 'dummyApiKey', token: 'dummyApiKey', @@ -13,11 +15,13 @@ const sampleDestination = { DisplayName: 'Mixpanel', ID: '1WhbSZ6uA3H5ChVifHpfL2H6sie', Name: 'MP', + Config: undefined, }, Enabled: true, ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }; const destinationWithSetOnceProperty = { diff --git a/test/integrations/destinations/mp/router/data.ts b/test/integrations/destinations/mp/router/data.ts index 0009e2c438..059e222e92 100644 --- a/test/integrations/destinations/mp/router/data.ts +++ b/test/integrations/destinations/mp/router/data.ts @@ -479,6 +479,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -546,6 +547,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -617,6 +619,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -680,6 +683,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -715,6 +719,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, ], @@ -1197,6 +1202,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -1263,6 +1269,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -1333,6 +1340,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -1396,6 +1404,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, { @@ -1431,6 +1440,7 @@ export const data = [ ID: '1WhcOCGgj9asZu850HvugU2C3Aq', Name: 'MP', Transformations: [], + WorkspaceID: '', }, }, ], diff --git a/test/integrations/destinations/the_trade_desk/common.ts b/test/integrations/destinations/the_trade_desk/common.ts index 8deaf60034..8425d56431 100644 --- a/test/integrations/destinations/the_trade_desk/common.ts +++ b/test/integrations/destinations/the_trade_desk/common.ts @@ -1,10 +1,15 @@ +import { Destination } from '../../../../src/types'; + const destType = 'the_trade_desk'; const destTypeInUpperCase = 'THE_TRADE_DESK'; const advertiserId = 'test-advertiser-id'; const dataProviderId = 'rudderstack'; const segmentName = 'test-segment'; + const trackerId = 'test-trackerId'; -const sampleDestination = { + +const sampleDestination: Destination = { + Config: { advertiserId, advertiserSecretKey: 'test-advertiser-secret-key', @@ -13,7 +18,17 @@ const sampleDestination = { audienceId: segmentName, trackerId, }, - DestinationDefinition: { Config: { cdkV2Enabled: true } }, + DestinationDefinition: { + Config: { cdkV2Enabled: true }, + ID: '123', + Name: 'TRADEDESK', + DisplayName: 'Trade Desk', + }, + ID: '345', + Name: 'Test', + Enabled: true, + WorkspaceID: '', + Transformations: [], }; const sampleSource = { diff --git a/test/integrations/testTypes.ts b/test/integrations/testTypes.ts index f181b00139..be063bbb68 100644 --- a/test/integrations/testTypes.ts +++ b/test/integrations/testTypes.ts @@ -1,5 +1,11 @@ import { AxiosResponse } from 'axios'; import MockAdapter from 'axios-mock-adapter'; +import { + ProcessorTransformationRequest, + ProcessorTransformationResponse, + RouterTransformationRequest, + RouterTransformationResponse, +} from '../../src/types'; export interface requestType { method: string; @@ -47,3 +53,50 @@ export type MockHttpCallsData = { httpReq: Record; httpRes: Partial; }; + +export type ProcessorTestData = { + id: string; + name: string; + description: string; + scenario: string; + successCriteria: string; + comment?: string; + feature: string; + module: string; + version: string; + input: { + request: { + body: ProcessorTransformationRequest[]; + }; + }; + output: { + response: { + status: number; + body: ProcessorTransformationResponse[]; + }; + }; +}; +export type RouterTestData = { + id: string; + name: string; + description: string; + comment?: string; + scenario: string; + successCriteria: string; + feature: string; + module: string; + version: string; + input: { + request: { + body: RouterTransformationRequest; + }; + }; + output: { + response: { + status: number; + body: { + output: RouterTransformationResponse[]; + }; + }; + }; +}; diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index ee6f76c29e..a47bf1a204 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -6,14 +6,18 @@ import MockAdapter from 'axios-mock-adapter'; import isMatch from 'lodash/isMatch'; import { OptionValues } from 'commander'; import { removeUndefinedAndNullValues } from '@rudderstack/integrations-lib'; -import { ProxyMetdata } from '../../src/types'; +import { Destination, Metadata, ProxyMetdata } from '../../src/types'; import { DeliveryV0ResponseSchema, DeliveryV0ResponseSchemaForOauth, DeliveryV1ResponseSchema, DeliveryV1ResponseSchemaForOauth, + ProcessorTransformationResponseListSchema, + ProcessorTransformationResponseSchema, ProxyV0RequestSchema, ProxyV1RequestSchema, + RouterTransformationResponseListSchema, + RouterTransformationResponseSchema, } from '../../src/types/zodTypes'; const generateAlphanumericId = (size = 36) => @@ -88,13 +92,13 @@ export const addMock = (mock: MockAdapter, axiosMock: MockHttpCallsData) => { break; } }; -export const overrideDestination = (destination, overrideConfigValues) => { +export const overrideDestination = (destination: Destination, overrideConfigValues) => { return Object.assign({}, destination, { Config: { ...destination.Config, ...overrideConfigValues }, }); }; -export const generateIndentifyPayload = (parametersOverride: any) => { +export const generateIndentifyPayload: any = (parametersOverride: any) => { const payload = { type: 'identify', sentAt: parametersOverride.sentAt || '2021-01-03T17:02:53.195Z', @@ -129,7 +133,7 @@ export const generateIndentifyPayload = (parametersOverride: any) => { return removeUndefinedAndNullValues(payload); }; -export const generateSimplifiedIdentifyPayload = (parametersOverride: any) => { +export const generateSimplifiedIdentifyPayload: any = (parametersOverride: any) => { return removeUndefinedAndNullValues({ type: 'identify', sentAt: parametersOverride.sentAt || '2021-01-03T17:02:53.195Z', @@ -147,7 +151,7 @@ export const generateSimplifiedIdentifyPayload = (parametersOverride: any) => { }); }; -export const generateTrackPayload = (parametersOverride: any) => { +export const generateTrackPayload: any = (parametersOverride: any) => { const payload = { type: 'track', sentAt: parametersOverride.sentAt || '2021-01-03T17:02:53.195Z', @@ -183,7 +187,7 @@ export const generateTrackPayload = (parametersOverride: any) => { return removeUndefinedAndNullValues(payload); }; -export const generateSimplifiedTrackPayload = (parametersOverride: any) => { +export const generateSimplifiedTrackPayload: any = (parametersOverride: any) => { return removeUndefinedAndNullValues({ type: 'track', sentAt: parametersOverride.sentAt || '2021-01-03T17:02:53.195Z', @@ -202,7 +206,7 @@ export const generateSimplifiedTrackPayload = (parametersOverride: any) => { }); }; -export const generatePageOrScreenPayload = (parametersOverride: any, eventType: string) => { +export const generatePageOrScreenPayload: any = (parametersOverride: any, eventType: string) => { const payload = { channel: 'web', userId: parametersOverride.userId || 'default-userId', @@ -255,7 +259,7 @@ export const generatePageOrScreenPayload = (parametersOverride: any, eventType: return removeUndefinedAndNullValues(payload); }; -export const generateSimplifiedPageOrScreenPayload = ( +export const generateSimplifiedPageOrScreenPayload: any = ( parametersOverride: any, eventType: string, ) => { @@ -277,7 +281,7 @@ export const generateSimplifiedPageOrScreenPayload = ( }); }; -export const generateGroupPayload = (parametersOverride: any) => { +export const generateGroupPayload: any = (parametersOverride: any) => { const payload = { channel: 'web', context: removeUndefinedAndNullValues({ @@ -320,7 +324,7 @@ export const generateGroupPayload = (parametersOverride: any) => { return removeUndefinedAndNullValues(payload); }; -export const generateSimplifiedGroupPayload = (parametersOverride: any) => { +export const generateSimplifiedGroupPayload: any = (parametersOverride: any) => { return removeUndefinedAndNullValues({ channel: 'web', userId: parametersOverride.userId || 'default-userId', @@ -338,7 +342,7 @@ export const generateSimplifiedGroupPayload = (parametersOverride: any) => { }); }; -export const transformResultBuilder = (matchData) => { +export const transformResultBuilder: any = (matchData) => { return removeUndefinedAndNullValues({ version: '1', type: 'REST', @@ -471,18 +475,18 @@ export const generateProxyV1Payload = ( export const validateTestWithZOD = (testPayload: TestCaseData, response: any) => { // Validate the resquest payload switch (testPayload.feature) { - // case 'router': - // RouterSchema.parse(responseBody); - // break; + case 'router': + RouterTransformationResponseListSchema.parse(response.body.output); + break; // case 'batch': // BatchScheam.parse(responseBody); // break; // case 'user_deletion': // DeletionSchema.parse(responseBody); // break; - // case 'processor': - // ProcessorSchema.parse(responseBody); - // break; + case 'processor': + ProcessorTransformationResponseListSchema.parse(response.body); + break; case 'dataDelivery': if (testPayload.version === 'v0') { ProxyV0RequestSchema.parse(testPayload.input.request.body); @@ -505,3 +509,16 @@ export const validateTestWithZOD = (testPayload: TestCaseData, response: any) => } return true; }; + +// ----------------------------- +// Helper functions + +export const generateMetadata = (jobId: number): any => { + return { + sourceId: 'default-sourceId', + workspaceId: 'default-workspaceId', + namespace: 'default-namespace', + destinationId: 'default-destinationId', + jobId, + }; +}; From 689b0cda0aeace910e82167375045e123e365300 Mon Sep 17 00:00:00 2001 From: ItsSudip Date: Thu, 8 Feb 2024 13:33:57 +0530 Subject: [PATCH 12/37] chore: update delivery test cases for criteo audience --- .../criteo_audience/dataDelivery/business.ts | 353 ++++++++++++ .../criteo_audience/dataDelivery/data.ts | 512 +----------------- .../criteo_audience/dataDelivery/oauth.ts | 176 ++++++ .../criteo_audience/dataDelivery/other.ts | 257 +++++++++ .../destinations/criteo_audience/network.ts | 206 ++++--- 5 files changed, 883 insertions(+), 621 deletions(-) create mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/business.ts create mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts create mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/other.ts diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/business.ts b/test/integrations/destinations/criteo_audience/dataDelivery/business.ts new file mode 100644 index 0000000000..80626442e1 --- /dev/null +++ b/test/integrations/destinations/criteo_audience/dataDelivery/business.ts @@ -0,0 +1,353 @@ +import { generateProxyV1Payload } from '../../../testUtils'; +export const headers = { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', +}; + +export const params = { + destination: 'criteo_audience', +}; +const method = 'PATCH'; + +const output = { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + }, + statusCode: 200, + }, + ], + }, + }, + }, +}; + +export const V1BusinessTestScenarion = [ + { + id: 'criteo_audience_business_0', + name: 'criteo_audience', + description: '[Business]:: Test for gum type audience with gumCallerId with success response', + successCriteria: 'Should return a 200 status code with a success message', + scenario: 'business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'remove', + identifierType: 'gum', + identifiers: ['sample_gum3'], + internalIdentifiers: false, + gumCallerId: '329739', + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', + }, + [ + { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + ], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + jobId: 1, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + }, + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'criteo_audience_business_1', + name: 'criteo_audience', + description: '[Business]:: Test for email type audience to add users with success response', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'email', + internalIdentifiers: false, + identifiers: [ + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + ], + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', + }, + [ + { + jobId: 2, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + ], + ), + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + jobId: 2, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + }, + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'criteo_audience_business_2', + name: 'criteo_audience', + description: '[Business]:: Test for mobile type audience to remove users with success response', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'remove', + identifierType: 'madid', + internalIdentifiers: false, + identifiers: [ + 'sample_madid', + 'sample_madid_1', + 'sample_madid_2', + 'sample_madid_10', + 'sample_madid_13', + 'sample_madid_11', + 'sample_madid_12', + ], + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34893/contactlist', + }, + [ + { + jobId: 3, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + ], + ), + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + jobId: 3, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + }, + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'criteo_audience_business_3', + name: 'criteo_audience', + description: '[Business]:: Test for mobile type audience where audienceId is invalid', + successCriteria: 'Should return a 400 status code with an error message', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', + }, + [ + { + jobId: 4, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, + }, + ], + ), + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'AudienceId is Invalid. Please Provide Valid AudienceId', + response: [ + { + error: + '{"errors":[{"traceIdentifier":"80a1a0ba3981b04da847d05700752c77","type":"authorization","code":"audience-invalid"}]}', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + jobId: 4, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + }, + statusCode: 400, + }, + ], + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + meta: 'instrumentation', + module: 'destination', + }, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts index fb5b689a96..72a76a7cf2 100644 --- a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts +++ b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts @@ -1,508 +1,4 @@ -export const data = [ - { - name: 'criteo_audience', - description: 'Test 0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'remove', - identifierType: 'gum', - identifiers: ['sample_gum3'], - internalIdentifiers: false, - gumCallerId: '329739', - }, - }, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'criteo_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: 'Request Processed Successfully', - destinationResponse: { - response: '', - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'criteo_audience', - description: 'Test 1', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'criteo_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 401, - body: { - output: { - status: 401, - authErrorCategory: 'REFRESH_TOKEN', - destinationResponse: { - errors: [ - { - traceIdentifier: '80a1a0ba3981b04da847d05700752c77', - type: 'authorization', - code: 'authorization-token-expired', - instance: '/2022-10/audiences/123/contactlist', - title: 'The authorization token has expired', - }, - ], - }, - message: - 'The authorization token has expired during criteo_audience response transformation', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - }, - }, - }, - }, - }, - { - name: 'criteo_audience', - description: 'Test 2', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'criteo_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 401, - body: { - output: { - status: 401, - authErrorCategory: 'REFRESH_TOKEN', - destinationResponse: { - errors: [ - { - traceIdentifier: '80a1a0ba3981b04da847d05700752c77', - type: 'authorization', - code: 'authorization-token-invalid', - instance: '/2022-10/audiences/123/contactlist', - title: 'The authorization header is invalid', - }, - ], - }, - message: - 'The authorization header is invalid during criteo_audience response transformation', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - }, - }, - }, - }, - }, - { - name: 'criteo_audience', - description: 'Test 3', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'criteo_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - message: 'AudienceId is Invalid. Please Provide Valid AudienceId', - destinationResponse: { - response: { - errors: [ - { - code: 'audience-invalid', - traceIdentifier: '80a1a0ba3981b04da847d05700752c77', - type: 'authorization', - }, - ], - }, - status: 404, - }, - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - meta: 'instrumentation', - module: 'destination', - }, - status: 400, - }, - }, - }, - }, - }, - { - name: 'criteo_audience', - description: 'Test 4', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34897/contactlist', - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'criteo_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 500, - body: { - output: { - destinationResponse: { - response: { - errors: [ - { - code: 'audience-invalid', - traceIdentifier: '80a1a0ba3981b04da847d05700752c77', - type: 'authorization', - }, - ], - }, - status: 503, - }, - message: 'Request Failed: during criteo_audience response transformation (Retryable)', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - feature: 'dataDelivery', - implementation: 'native', - errorType: 'retryable', - module: 'destination', - }, - status: 500, - }, - }, - }, - }, - }, - { - name: 'criteo_audience', - description: 'Test 5', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34898/contactlist', - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'criteo_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 429, - body: { - output: { - destinationResponse: { - response: {}, - status: 429, - }, - message: - 'Request Failed: during criteo_audience response transformation - due to Request Limit exceeded, (Throttled)', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'throttled', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - status: 429, - }, - }, - }, - }, - }, - { - name: 'criteo_audience', - description: 'Test 6', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'criteo_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - destinationResponse: { - response: { - message: 'unknown error', - }, - status: 410, - }, - message: - 'Request Failed: during criteo_audience response transformation with status "410" due to "{"message":"unknown error"}", (Aborted) ', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - status: 400, - }, - }, - }, - }, - }, -]; +import { V1BusinessTestScenarion } from './business'; +import { v1OauthScenarios } from './oauth'; +import { v1OtherScenarios } from './other'; +export const data = [...V1BusinessTestScenarion, ...v1OauthScenarios, ...v1OtherScenarios]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts b/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts new file mode 100644 index 0000000000..6e021f9b19 --- /dev/null +++ b/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts @@ -0,0 +1,176 @@ +import { params, headers } from './business'; +import { generateProxyV1Payload } from '../../../testUtils'; +export const v1OauthScenarios = [ + { + id: 'criteo_audience_oauth_0', + name: 'criteo_audience', + description: '[OAUTH]:: Test expired access token', + successCriteria: 'Should return a 401 status code with authErrorCategory as REFRESH_TOKEN', + scenario: 'oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params, + headers, + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', + }, + [ + { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + jobId: 1, + }, + ], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + status: 401, + authErrorCategory: 'REFRESH_TOKEN', + response: [ + { + error: + 'The authorization token has expired during criteo_audience response transformation', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + jobId: 1, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + }, + statusCode: 401, + }, + ], + message: + 'The authorization token has expired during criteo_audience response transformation', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + }, + }, + }, + }, + }, + }, + { + id: 'criteo_audience_oauth_1', + name: 'criteo_audience', + description: '[OAUTH]:: Test invalid access token', + successCriteria: 'Should return a 401 status code with authErrorCategory as REFRESH_TOKEN', + scenario: 'oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params, + headers, + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', + }, + [ + { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + jobId: 2, + }, + ], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + status: 401, + authErrorCategory: 'REFRESH_TOKEN', + response: [ + { + error: + 'The authorization header is invalid during criteo_audience response transformation', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + jobId: 2, + }, + statusCode: 401, + }, + ], + message: + 'The authorization header is invalid during criteo_audience response transformation', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + }, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/other.ts b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts new file mode 100644 index 0000000000..4b9a37a4ae --- /dev/null +++ b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts @@ -0,0 +1,257 @@ +import { params, headers } from './business'; +import { generateProxyV1Payload } from '../../../testUtils'; + +export const v1OtherScenarios = [ + { + id: 'criteo_audience_other_0', + name: 'criteo_audience', + description: '[Other]:: Test for checking service unavailable scenario', + successCriteria: 'Should return a 500 status code with', + scenario: 'other', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/34897/contactlist', + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + }, + [ + { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + jobId: 1, + }, + ], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + response: [ + { + error: + '{"errors":[{"traceIdentifier":"80a1a0ba3981b04da847d05700752c77","type":"authorization","code":"audience-invalid"}]}', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + jobId: 1, + }, + statusCode: 500, + }, + ], + message: 'Request Failed: during criteo_audience response transformation (Retryable)', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + feature: 'dataDelivery', + implementation: 'native', + errorType: 'retryable', + module: 'destination', + }, + }, + }, + }, + }, + }, + { + id: 'criteo_audience_other_1', + name: 'criteo_audience', + description: '[Other]:: Test for checking throttling scenario', + successCriteria: 'Should return a 429 status code', + scenario: 'other', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/34898/contactlist', + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + }, + [ + { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + jobId: 2, + }, + ], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 429, + response: [ + { + error: '{}', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + jobId: 2, + }, + statusCode: 429, + }, + ], + message: + 'Request Failed: during criteo_audience response transformation - due to Request Limit exceeded, (Throttled)', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + feature: 'dataDelivery', + implementation: 'native', + errorType: 'throttled', + module: 'destination', + }, + }, + }, + }, + }, + }, + { + id: 'criteo_audience_other_2', + name: 'criteo_audience', + description: '[Other]:: Test for checking unknown error scenario', + successCriteria: 'Should return a 410 status code and abort the event', + scenario: 'other', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + method: 'PATCH', + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + endpoint: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', + }, + [ + { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + jobId: 3, + }, + ], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + response: [ + { + error: '{"message":"unknown error"}', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + jobId: 3, + }, + statusCode: 400, + }, + ], + message: + 'Request Failed: during criteo_audience response transformation with status "410" due to "{"message":"unknown error"}", (Aborted) ', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + }, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/network.ts b/test/integrations/destinations/criteo_audience/network.ts index 959e8a2112..d259d9752e 100644 --- a/test/integrations/destinations/criteo_audience/network.ts +++ b/test/integrations/destinations/criteo_audience/network.ts @@ -1,3 +1,23 @@ +const headers = { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', +}; +const params = { destination: 'criteo_audience' }; +const method = 'PATCH'; +const commonData = { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, +}; + export const networkCallsData = [ { httpReq: { @@ -14,39 +34,74 @@ export const networkCallsData = [ }, }, }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + params, + headers, + method, }, httpRes: { status: 200 }, }, { httpReq: { - url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', + url: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', data: { data: { type: 'ContactlistAmendment', attributes: { operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + identifierType: 'email', internalIdentifiers: false, + identifiers: [ + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + ], }, }, }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', + params, + headers, + method, + }, + httpRes: { status: 200 }, + }, + { + httpReq: { + url: 'https://api.criteo.com/2022-10/audiences/34893/contactlist', + data: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'remove', + identifierType: 'madid', + internalIdentifiers: false, + identifiers: [ + 'sample_madid', + 'sample_madid_1', + 'sample_madid_2', + 'sample_madid_10', + 'sample_madid_13', + 'sample_madid_11', + 'sample_madid_12', + ], + }, + }, }, - method: 'PATCH', + params, + headers, + method, + }, + httpRes: { status: 200 }, + }, + { + httpReq: { + url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', + data: commonData, + params, + headers, + method, }, httpRes: { code: '400', @@ -67,25 +122,10 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '400', @@ -106,25 +146,10 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '400', @@ -143,25 +168,10 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34897/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '500', @@ -180,50 +190,20 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34898/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '429', data: {}, status: 429 }, }, { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '400', data: { message: 'unknown error' }, status: 410 }, }, From 9e047747a119cd3e23cb0c352363788f40e0ef42 Mon Sep 17 00:00:00 2001 From: ItsSudip Date: Thu, 8 Feb 2024 13:37:03 +0530 Subject: [PATCH 13/37] Revert "chore: update delivery test cases for criteo audience" This reverts commit 689b0cda0aeace910e82167375045e123e365300. --- .../criteo_audience/dataDelivery/business.ts | 353 ------------ .../criteo_audience/dataDelivery/data.ts | 512 +++++++++++++++++- .../criteo_audience/dataDelivery/oauth.ts | 176 ------ .../criteo_audience/dataDelivery/other.ts | 257 --------- .../destinations/criteo_audience/network.ts | 206 +++---- 5 files changed, 621 insertions(+), 883 deletions(-) delete mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/business.ts delete mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts delete mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/other.ts diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/business.ts b/test/integrations/destinations/criteo_audience/dataDelivery/business.ts deleted file mode 100644 index 80626442e1..0000000000 --- a/test/integrations/destinations/criteo_audience/dataDelivery/business.ts +++ /dev/null @@ -1,353 +0,0 @@ -import { generateProxyV1Payload } from '../../../testUtils'; -export const headers = { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', -}; - -export const params = { - destination: 'criteo_audience', -}; -const method = 'PATCH'; - -const output = { - response: { - status: 200, - body: { - output: { - status: 200, - message: 'Request Processed Successfully', - response: [ - { - error: '""', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - }, - statusCode: 200, - }, - ], - }, - }, - }, -}; - -export const V1BusinessTestScenarion = [ - { - id: 'criteo_audience_business_0', - name: 'criteo_audience', - description: '[Business]:: Test for gum type audience with gumCallerId with success response', - successCriteria: 'Should return a 200 status code with a success message', - scenario: 'business', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: generateProxyV1Payload( - { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'remove', - identifierType: 'gum', - identifiers: ['sample_gum3'], - internalIdentifiers: false, - gumCallerId: '329739', - }, - }, - }, - params, - headers, - method, - endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', - }, - [ - { - jobId: 1, - attemptNum: 1, - userId: 'dummyUserId', - sourceId: 'dummySourceId', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - secret: {}, - dontBatch: false, - }, - ], - ), - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: 'Request Processed Successfully', - response: [ - { - error: '""', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - jobId: 1, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - }, - statusCode: 200, - }, - ], - }, - }, - }, - }, - }, - { - id: 'criteo_audience_business_1', - name: 'criteo_audience', - description: '[Business]:: Test for email type audience to add users with success response', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: generateProxyV1Payload( - { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'email', - internalIdentifiers: false, - identifiers: [ - 'alex@email.com', - 'amy@email.com', - 'van@email.com', - 'alex@email.com', - 'amy@email.com', - 'van@email.com', - ], - }, - }, - }, - params, - headers, - method, - endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', - }, - [ - { - jobId: 2, - attemptNum: 1, - userId: 'dummyUserId', - sourceId: 'dummySourceId', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - secret: {}, - dontBatch: false, - }, - ], - ), - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: 'Request Processed Successfully', - response: [ - { - error: '""', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - jobId: 2, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - }, - statusCode: 200, - }, - ], - }, - }, - }, - }, - }, - { - id: 'criteo_audience_business_2', - name: 'criteo_audience', - description: '[Business]:: Test for mobile type audience to remove users with success response', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: generateProxyV1Payload( - { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'remove', - identifierType: 'madid', - internalIdentifiers: false, - identifiers: [ - 'sample_madid', - 'sample_madid_1', - 'sample_madid_2', - 'sample_madid_10', - 'sample_madid_13', - 'sample_madid_11', - 'sample_madid_12', - ], - }, - }, - }, - params, - headers, - method, - endpoint: 'https://api.criteo.com/2022-10/audiences/34893/contactlist', - }, - [ - { - jobId: 3, - attemptNum: 1, - userId: 'dummyUserId', - sourceId: 'dummySourceId', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - secret: {}, - dontBatch: false, - }, - ], - ), - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: 'Request Processed Successfully', - response: [ - { - error: '""', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - jobId: 3, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - }, - statusCode: 200, - }, - ], - }, - }, - }, - }, - }, - { - id: 'criteo_audience_business_3', - name: 'criteo_audience', - description: '[Business]:: Test for mobile type audience where audienceId is invalid', - successCriteria: 'Should return a 400 status code with an error message', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: generateProxyV1Payload( - { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params, - headers, - method, - endpoint: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', - }, - [ - { - jobId: 4, - attemptNum: 1, - userId: 'dummyUserId', - sourceId: 'dummySourceId', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - secret: {}, - dontBatch: false, - }, - ], - ), - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 400, - message: 'AudienceId is Invalid. Please Provide Valid AudienceId', - response: [ - { - error: - '{"errors":[{"traceIdentifier":"80a1a0ba3981b04da847d05700752c77","type":"authorization","code":"audience-invalid"}]}', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - jobId: 4, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - }, - statusCode: 400, - }, - ], - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - meta: 'instrumentation', - module: 'destination', - }, - }, - }, - }, - }, - }, -]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts index 72a76a7cf2..fb5b689a96 100644 --- a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts +++ b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts @@ -1,4 +1,508 @@ -import { V1BusinessTestScenarion } from './business'; -import { v1OauthScenarios } from './oauth'; -import { v1OtherScenarios } from './other'; -export const data = [...V1BusinessTestScenarion, ...v1OauthScenarios, ...v1OtherScenarios]; +export const data = [ + { + name: 'criteo_audience', + description: 'Test 0', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'remove', + identifierType: 'gum', + identifiers: ['sample_gum3'], + internalIdentifiers: false, + gumCallerId: '329739', + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + params: { + destination: 'criteo_audience', + }, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + destinationResponse: { + response: '', + status: 200, + }, + }, + }, + }, + }, + }, + { + name: 'criteo_audience', + description: 'Test 1', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + params: { + destination: 'criteo_audience', + }, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + status: 401, + authErrorCategory: 'REFRESH_TOKEN', + destinationResponse: { + errors: [ + { + traceIdentifier: '80a1a0ba3981b04da847d05700752c77', + type: 'authorization', + code: 'authorization-token-expired', + instance: '/2022-10/audiences/123/contactlist', + title: 'The authorization token has expired', + }, + ], + }, + message: + 'The authorization token has expired during criteo_audience response transformation', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'Non-determininable', + workspaceId: 'Non-determininable', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + }, + }, + }, + }, + }, + }, + { + name: 'criteo_audience', + description: 'Test 2', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + params: { + destination: 'criteo_audience', + }, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + status: 401, + authErrorCategory: 'REFRESH_TOKEN', + destinationResponse: { + errors: [ + { + traceIdentifier: '80a1a0ba3981b04da847d05700752c77', + type: 'authorization', + code: 'authorization-token-invalid', + instance: '/2022-10/audiences/123/contactlist', + title: 'The authorization header is invalid', + }, + ], + }, + message: + 'The authorization header is invalid during criteo_audience response transformation', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'Non-determininable', + workspaceId: 'Non-determininable', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + }, + }, + }, + }, + }, + }, + { + name: 'criteo_audience', + description: 'Test 3', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + params: { + destination: 'criteo_audience', + }, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + message: 'AudienceId is Invalid. Please Provide Valid AudienceId', + destinationResponse: { + response: { + errors: [ + { + code: 'audience-invalid', + traceIdentifier: '80a1a0ba3981b04da847d05700752c77', + type: 'authorization', + }, + ], + }, + status: 404, + }, + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'Non-determininable', + workspaceId: 'Non-determininable', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + meta: 'instrumentation', + module: 'destination', + }, + status: 400, + }, + }, + }, + }, + }, + { + name: 'criteo_audience', + description: 'Test 4', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/34897/contactlist', + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + params: { + destination: 'criteo_audience', + }, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + destinationResponse: { + response: { + errors: [ + { + code: 'audience-invalid', + traceIdentifier: '80a1a0ba3981b04da847d05700752c77', + type: 'authorization', + }, + ], + }, + status: 503, + }, + message: 'Request Failed: during criteo_audience response transformation (Retryable)', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'Non-determininable', + workspaceId: 'Non-determininable', + feature: 'dataDelivery', + implementation: 'native', + errorType: 'retryable', + module: 'destination', + }, + status: 500, + }, + }, + }, + }, + }, + { + name: 'criteo_audience', + description: 'Test 5', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/34898/contactlist', + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + params: { + destination: 'criteo_audience', + }, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 429, + body: { + output: { + destinationResponse: { + response: {}, + status: 429, + }, + message: + 'Request Failed: during criteo_audience response transformation - due to Request Limit exceeded, (Throttled)', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'Non-determininable', + workspaceId: 'Non-determininable', + errorType: 'throttled', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + }, + status: 429, + }, + }, + }, + }, + }, + { + name: 'criteo_audience', + description: 'Test 6', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + version: '1', + type: 'REST', + method: 'PATCH', + endpoint: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + params: { + destination: 'criteo_audience', + }, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + destinationResponse: { + response: { + message: 'unknown error', + }, + status: 410, + }, + message: + 'Request Failed: during criteo_audience response transformation with status "410" due to "{"message":"unknown error"}", (Aborted) ', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'Non-determininable', + workspaceId: 'Non-determininable', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + }, + status: 400, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts b/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts deleted file mode 100644 index 6e021f9b19..0000000000 --- a/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { params, headers } from './business'; -import { generateProxyV1Payload } from '../../../testUtils'; -export const v1OauthScenarios = [ - { - id: 'criteo_audience_oauth_0', - name: 'criteo_audience', - description: '[OAUTH]:: Test expired access token', - successCriteria: 'Should return a 401 status code with authErrorCategory as REFRESH_TOKEN', - scenario: 'oauth', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: generateProxyV1Payload( - { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params, - headers, - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', - }, - [ - { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - jobId: 1, - }, - ], - ), - method: 'POST', - }, - }, - output: { - response: { - status: 401, - body: { - output: { - status: 401, - authErrorCategory: 'REFRESH_TOKEN', - response: [ - { - error: - 'The authorization token has expired during criteo_audience response transformation', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - jobId: 1, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - }, - statusCode: 401, - }, - ], - message: - 'The authorization token has expired during criteo_audience response transformation', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - }, - }, - }, - }, - }, - { - id: 'criteo_audience_oauth_1', - name: 'criteo_audience', - description: '[OAUTH]:: Test invalid access token', - successCriteria: 'Should return a 401 status code with authErrorCategory as REFRESH_TOKEN', - scenario: 'oauth', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: generateProxyV1Payload( - { - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params, - headers, - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', - }, - [ - { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - jobId: 2, - }, - ], - ), - method: 'POST', - }, - }, - output: { - response: { - status: 401, - body: { - output: { - status: 401, - authErrorCategory: 'REFRESH_TOKEN', - response: [ - { - error: - 'The authorization header is invalid during criteo_audience response transformation', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - jobId: 2, - }, - statusCode: 401, - }, - ], - message: - 'The authorization header is invalid during criteo_audience response transformation', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - }, - }, - }, - }, - }, -]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/other.ts b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts deleted file mode 100644 index 4b9a37a4ae..0000000000 --- a/test/integrations/destinations/criteo_audience/dataDelivery/other.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { params, headers } from './business'; -import { generateProxyV1Payload } from '../../../testUtils'; - -export const v1OtherScenarios = [ - { - id: 'criteo_audience_other_0', - name: 'criteo_audience', - description: '[Other]:: Test for checking service unavailable scenario', - successCriteria: 'Should return a 500 status code with', - scenario: 'other', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: generateProxyV1Payload( - { - headers, - params, - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34897/contactlist', - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - }, - [ - { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - jobId: 1, - }, - ], - ), - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 500, - response: [ - { - error: - '{"errors":[{"traceIdentifier":"80a1a0ba3981b04da847d05700752c77","type":"authorization","code":"audience-invalid"}]}', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - jobId: 1, - }, - statusCode: 500, - }, - ], - message: 'Request Failed: during criteo_audience response transformation (Retryable)', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - feature: 'dataDelivery', - implementation: 'native', - errorType: 'retryable', - module: 'destination', - }, - }, - }, - }, - }, - }, - { - id: 'criteo_audience_other_1', - name: 'criteo_audience', - description: '[Other]:: Test for checking throttling scenario', - successCriteria: 'Should return a 429 status code', - scenario: 'other', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: generateProxyV1Payload( - { - headers, - params, - method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34898/contactlist', - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - }, - [ - { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - jobId: 2, - }, - ], - ), - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 429, - response: [ - { - error: '{}', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - jobId: 2, - }, - statusCode: 429, - }, - ], - message: - 'Request Failed: during criteo_audience response transformation - due to Request Limit exceeded, (Throttled)', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - feature: 'dataDelivery', - implementation: 'native', - errorType: 'throttled', - module: 'destination', - }, - }, - }, - }, - }, - }, - { - id: 'criteo_audience_other_2', - name: 'criteo_audience', - description: '[Other]:: Test for checking unknown error scenario', - successCriteria: 'Should return a 410 status code and abort the event', - scenario: 'other', - feature: 'dataDelivery', - module: 'destination', - version: 'v1', - input: { - request: { - body: generateProxyV1Payload( - { - headers, - params, - method: 'PATCH', - JSON: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - endpoint: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', - }, - [ - { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - jobId: 3, - }, - ], - ), - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 400, - response: [ - { - error: '{"message":"unknown error"}', - metadata: { - attemptNum: 1, - destinationId: 'dummyDestinationId', - dontBatch: false, - secret: {}, - sourceId: 'dummySourceId', - userId: 'dummyUserId', - workspaceId: 'dummyWorkspaceId', - jobId: 3, - }, - statusCode: 400, - }, - ], - message: - 'Request Failed: during criteo_audience response transformation with status "410" due to "{"message":"unknown error"}", (Aborted) ', - statTags: { - destType: 'CRITEO_AUDIENCE', - errorCategory: 'network', - destinationId: 'dummyDestinationId', - workspaceId: 'dummyWorkspaceId', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - }, - }, - }, - }, - }, -]; diff --git a/test/integrations/destinations/criteo_audience/network.ts b/test/integrations/destinations/criteo_audience/network.ts index d259d9752e..959e8a2112 100644 --- a/test/integrations/destinations/criteo_audience/network.ts +++ b/test/integrations/destinations/criteo_audience/network.ts @@ -1,23 +1,3 @@ -const headers = { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', -}; -const params = { destination: 'criteo_audience' }; -const method = 'PATCH'; -const commonData = { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, -}; - export const networkCallsData = [ { httpReq: { @@ -34,74 +14,39 @@ export const networkCallsData = [ }, }, }, - params, - headers, - method, - }, - httpRes: { status: 200 }, - }, - { - httpReq: { - url: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'email', - internalIdentifiers: false, - identifiers: [ - 'alex@email.com', - 'amy@email.com', - 'van@email.com', - 'alex@email.com', - 'amy@email.com', - 'van@email.com', - ], - }, - }, + params: { destination: 'criteo_audience' }, + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', }, - params, - headers, - method, + method: 'PATCH', }, httpRes: { status: 200 }, }, { httpReq: { - url: 'https://api.criteo.com/2022-10/audiences/34893/contactlist', + url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', data: { data: { type: 'ContactlistAmendment', attributes: { - operation: 'remove', + operation: 'add', identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], internalIdentifiers: false, - identifiers: [ - 'sample_madid', - 'sample_madid_1', - 'sample_madid_2', - 'sample_madid_10', - 'sample_madid_13', - 'sample_madid_11', - 'sample_madid_12', - ], }, }, }, - params, - headers, - method, - }, - httpRes: { status: 200 }, - }, - { - httpReq: { - url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', - data: commonData, - params, - headers, - method, + params: { destination: 'criteo_audience' }, + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'PATCH', }, httpRes: { code: '400', @@ -122,10 +67,25 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', - data: commonData, - params, - headers, - method, + data: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params: { destination: 'criteo_audience' }, + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'PATCH', }, httpRes: { code: '400', @@ -146,10 +106,25 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', - data: commonData, - params, - headers, - method, + data: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params: { destination: 'criteo_audience' }, + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'PATCH', }, httpRes: { code: '400', @@ -168,10 +143,25 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34897/contactlist', - data: commonData, - params, - headers, - method, + data: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params: { destination: 'criteo_audience' }, + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'PATCH', }, httpRes: { code: '500', @@ -190,20 +180,50 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34898/contactlist', - data: commonData, - params, - headers, - method, + data: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params: { destination: 'criteo_audience' }, + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'PATCH', }, httpRes: { code: '429', data: {}, status: 429 }, }, { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', - data: commonData, - params, - headers, - method, + data: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params: { destination: 'criteo_audience' }, + headers: { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'PATCH', }, httpRes: { code: '400', data: { message: 'unknown error' }, status: 410 }, }, From 7114f9b97894ff67c3b01d0bee077e2097e898f8 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Fri, 9 Feb 2024 11:45:22 +0530 Subject: [PATCH 14/37] chore: add type def for proxy v1 test --- src/types/index.ts | 1 + .../campaign_manager/dataDelivery/business.ts | 3 +- .../campaign_manager/dataDelivery/oauth.ts | 3 +- .../campaign_manager/dataDelivery/other.ts | 3 +- test/integrations/testTypes.ts | 28 +++++++++++++++++++ test/integrations/testUtils.ts | 18 ++++++++---- 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/types/index.ts b/src/types/index.ts index b81071476d..68dfe3870d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -205,6 +205,7 @@ type DeliveryV1Response = { status: number; message: string; statTags?: object; + destinationResponse?: any; authErrorCategory?: string; response: DeliveryJobState[]; }; diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts index 6e66650577..e663f3212a 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts @@ -1,4 +1,5 @@ import { ProxyMetdata } from '../../../../../src/types'; +import { ProxyV1TestData } from '../../../testTypes'; import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils'; // Boilerplate data for the test cases @@ -281,7 +282,7 @@ export const testScenariosForV0API = [ }, ]; -export const testScenariosForV1API = [ +export const testScenariosForV1API: ProxyV1TestData[] = [ { id: 'cm360_v1_scenario_1', name: 'campaign_manager', diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts index eaa29f5c37..929af485d8 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts @@ -1,3 +1,4 @@ +import { ProxyV1TestData } from '../../../testTypes'; import { generateProxyV1Payload, generateProxyV0Payload } from '../../../testUtils'; // Boilerplat data for the test cases // ====================================== @@ -302,7 +303,7 @@ export const v0oauthScenarios = [ }, ]; -export const v1oauthScenarios = [ +export const v1oauthScenarios: ProxyV1TestData[] = [ { id: 'cm360_v1_oauth_scenario_1', name: 'campaign_manager', diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts index e280d89959..709f55a4c0 100644 --- a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts +++ b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts @@ -1,3 +1,4 @@ +import { ProxyV1TestData } from '../../../testTypes'; import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils'; export const otherScenariosV0 = [ @@ -231,7 +232,7 @@ export const otherScenariosV0 = [ }, ]; -export const otherScenariosV1 = [ +export const otherScenariosV1: ProxyV1TestData[] = [ { id: 'cm360_v1_other_scenario_1', name: 'campaign_manager', diff --git a/test/integrations/testTypes.ts b/test/integrations/testTypes.ts index be063bbb68..a46277d552 100644 --- a/test/integrations/testTypes.ts +++ b/test/integrations/testTypes.ts @@ -1,8 +1,10 @@ import { AxiosResponse } from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { + DeliveryV1Response, ProcessorTransformationRequest, ProcessorTransformationResponse, + ProxyV1Request, RouterTransformationRequest, RouterTransformationResponse, } from '../../src/types'; @@ -100,3 +102,29 @@ export type RouterTestData = { }; }; }; + +export type ProxyV1TestData = { + id: string; + name: string; + description: string; + comment?: string; + scenario: string; + successCriteria: string; + feature: string; + module: string; + version: string; + input: { + request: { + body: ProxyV1Request; + method: string; + }; + }; + output: { + response: { + status: number; + body: { + output: DeliveryV1Response; + }; + }; + }; +}; diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index a47bf1a204..8905f7bfe2 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -6,7 +6,13 @@ import MockAdapter from 'axios-mock-adapter'; import isMatch from 'lodash/isMatch'; import { OptionValues } from 'commander'; import { removeUndefinedAndNullValues } from '@rudderstack/integrations-lib'; -import { Destination, Metadata, ProxyMetdata } from '../../src/types'; +import { + Destination, + Metadata, + ProxyMetdata, + ProxyV0Request, + ProxyV1Request, +} from '../../src/types'; import { DeliveryV0ResponseSchema, DeliveryV0ResponseSchemaForOauth, @@ -67,7 +73,7 @@ export const addMock = (mock: MockAdapter, axiosMock: MockHttpCallsData) => { switch (method.toLowerCase()) { case 'get': - // We are accepting parameters exclusively for mocking purposes and do not require a request body, + // We are accepting parameters exclusively for mocking purposes and do not require a request body, // particularly for GET requests where it is typically unnecessary // @ts-ignore mock.onGet(url, { params }, headersAsymMatch).reply(status, data, headers); @@ -389,7 +395,7 @@ export const generateProxyV0Payload = ( payloadParameters: any, metadataInput?: ProxyMetdata, destinationConfig?: any, -) => { +): ProxyV0Request => { let metadata: ProxyMetdata = { jobId: 1, attemptNum: 1, @@ -423,14 +429,14 @@ export const generateProxyV0Payload = ( metadata, destinationConfig: destinationConfig || {}, }; - return removeUndefinedAndNullValues(payload); + return removeUndefinedAndNullValues(payload) as ProxyV0Request; }; export const generateProxyV1Payload = ( payloadParameters: any, metadataInput?: ProxyMetdata[], destinationConfig?: any, -) => { +): ProxyV1Request => { let metadata: ProxyMetdata[] = [ { jobId: 1, @@ -466,7 +472,7 @@ export const generateProxyV1Payload = ( metadata, destinationConfig: destinationConfig || {}, }; - return removeUndefinedAndNullValues(payload); + return removeUndefinedAndNullValues(payload) as ProxyV1Request; }; // ----------------------------- From 33d4d62e74834b33e34841ba9a86a89c0b980911 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Fri, 9 Feb 2024 12:40:45 +0530 Subject: [PATCH 15/37] chore: fix generateMetdata func --- test/integrations/testUtils.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index 8905f7bfe2..683f9dbe3b 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -521,10 +521,13 @@ export const validateTestWithZOD = (testPayload: TestCaseData, response: any) => export const generateMetadata = (jobId: number): any => { return { + jobId, + attemptNum: 1, + userId: 'default-userId', sourceId: 'default-sourceId', - workspaceId: 'default-workspaceId', - namespace: 'default-namespace', destinationId: 'default-destinationId', - jobId, + workspaceId: 'default-workspaceId', + secret: {}, + dontBatch: false, }; }; From 455dce7acbee39f1ff0e2e8eda86a71cca5c2e65 Mon Sep 17 00:00:00 2001 From: Sudip Paul <67197965+ItsSudip@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:41:28 +0530 Subject: [PATCH 16/37] chore: criteo audience update proxy test (#3068) * chore: update delivery test cases for criteo audience --- test/integrations/common/criteo/network.ts | 72 +++++ test/integrations/common/network.ts | 24 +- test/integrations/component.test.ts | 2 +- .../criteo_audience/dataDelivery/business.ts | 255 ++++++++++++++++++ .../criteo_audience/dataDelivery/data.ts | 63 +++-- .../criteo_audience/dataDelivery/oauth.ts | 133 +++++++++ .../criteo_audience/dataDelivery/other.ts | 196 ++++++++++++++ .../destinations/criteo_audience/network.ts | 204 +++++--------- 8 files changed, 796 insertions(+), 153 deletions(-) create mode 100644 test/integrations/common/criteo/network.ts create mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/business.ts create mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts create mode 100644 test/integrations/destinations/criteo_audience/dataDelivery/other.ts diff --git a/test/integrations/common/criteo/network.ts b/test/integrations/common/criteo/network.ts new file mode 100644 index 0000000000..cd5e1ca1e8 --- /dev/null +++ b/test/integrations/common/criteo/network.ts @@ -0,0 +1,72 @@ +const headers = { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', +}; +const params = { destination: 'criteo_audience' }; +const method = 'PATCH'; +const commonData = { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, +}; + +export const networkCallsData = [ + { + description: 'Mock response depicting expired access token error', + httpReq: { + url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken', + data: commonData, + params, + headers, + method, + }, + httpRes: { + code: '400', + data: { + errors: [ + { + traceIdentifier: '80a1a0ba3981b04da847d05700752c77', + type: 'authorization', + code: 'authorization-token-expired', + instance: '/2022-10/audiences/123/contactlist', + title: 'The authorization token has expired', + }, + ], + }, + status: 401, + }, + }, + { + description: 'Mock response depicting invalid access token error', + httpReq: { + url: 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken', + data: commonData, + params, + headers, + method, + }, + httpRes: { + code: '400', + data: { + errors: [ + { + traceIdentifier: '80a1a0ba3981b04da847d05700752c77', + type: 'authorization', + code: 'authorization-token-invalid', + instance: '/2022-10/audiences/123/contactlist', + title: 'The authorization header is invalid', + }, + ], + }, + status: 401, + }, + }, +]; diff --git a/test/integrations/common/network.ts b/test/integrations/common/network.ts index 8f80e406ae..8b0ed16c72 100644 --- a/test/integrations/common/network.ts +++ b/test/integrations/common/network.ts @@ -17,7 +17,18 @@ export const networkCallsData = [ }, }, { - description: 'Mock response depicting INTERNAL SERVER ERROR error', + description: 'Mock response depicting INTERNAL SERVER ERROR error with post method', + httpReq: { + method: 'post', + url: 'https://random_test_url/test_for_internal_server_error', + }, + httpRes: { + data: 'Internal Server Error', + status: 500, + }, + }, + { + description: 'Mock response depicting INTERNAL SERVER ERROR error with patch method', httpReq: { method: 'post', url: 'https://random_test_url/test_for_internal_server_error', @@ -59,4 +70,15 @@ export const networkCallsData = [ data: null, }, }, + { + description: 'Mock response depicting TOO MANY REQUESTS error with patch method', + httpReq: { + method: 'patch', + url: 'https://random_test_url/test_for_too_many_requests', + }, + httpRes: { + data: {}, + status: 429, + }, + }, ]; diff --git a/test/integrations/component.test.ts b/test/integrations/component.test.ts index aaaa536d91..388c283c61 100644 --- a/test/integrations/component.test.ts +++ b/test/integrations/component.test.ts @@ -54,7 +54,7 @@ if (opts.generate === 'true') { let server: Server; -const INTEGRATIONS_WITH_UPDATED_TEST_STRUCTURE = ['klaviyo', 'campaign_manager']; +const INTEGRATIONS_WITH_UPDATED_TEST_STRUCTURE = ['klaviyo', 'campaign_manager', 'criteo_audience']; beforeAll(async () => { initaliseReport(); diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/business.ts b/test/integrations/destinations/criteo_audience/dataDelivery/business.ts new file mode 100644 index 0000000000..f30bf73d7a --- /dev/null +++ b/test/integrations/destinations/criteo_audience/dataDelivery/business.ts @@ -0,0 +1,255 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; +export const headers = { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', +}; +export const params = { + destination: 'criteo_audience', +}; +const method = 'PATCH'; + +export const V1BusinessTestScenarion: ProxyV1TestData[] = [ + { + id: 'criteo_audience_business_0', + name: 'criteo_audience', + description: '[Business]:: Test for gum type audience with gumCallerId with success response', + successCriteria: 'Should return a 200 status code with a success message', + scenario: 'business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'remove', + identifierType: 'gum', + identifiers: ['sample_gum3'], + internalIdentifiers: false, + gumCallerId: '329739', + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: generateMetadata(1), + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'criteo_audience_business_1', + name: 'criteo_audience', + scenario: 'business', + description: '[Business]:: Test for email type audience to add users with success response', + successCriteria: 'Should return a 200 status code with a success message', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + method: 'POST', + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'email', + internalIdentifiers: false, + identifiers: [ + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + ], + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', + }, + [generateMetadata(2)], + ), + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: generateMetadata(2), + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'criteo_audience_business_2', + name: 'criteo_audience', + scenario: 'business', + description: '[Business]:: Test for mobile type audience to remove users with success response', + successCriteria: 'Should return a 200 status code with a success message', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + method: 'POST', + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'remove', + identifierType: 'madid', + internalIdentifiers: false, + identifiers: [ + 'sample_madid', + 'sample_madid_1', + 'sample_madid_2', + 'sample_madid_10', + 'sample_madid_13', + 'sample_madid_11', + 'sample_madid_12', + ], + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34893/contactlist', + }, + [generateMetadata(3)], + ), + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '""', + metadata: generateMetadata(3), + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'criteo_audience_business_3', + name: 'criteo_audience', + scenario: 'business', + description: '[Business]:: Test for mobile type audience where audienceId is invalid', + successCriteria: + 'Should return a 400 status code with an error audience-invalid. It should also have the invalid audienceId in the error message as follows: "Audience is invalid"', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + method: 'POST', + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params, + headers, + method, + endpoint: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', + }, + [generateMetadata(4)], + ), + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'AudienceId is Invalid. Please Provide Valid AudienceId', + response: [ + { + error: + '{"errors":[{"traceIdentifier":"80a1a0ba3981b04da847d05700752c77","type":"authorization","code":"audience-invalid"}]}', + metadata: generateMetadata(4), + statusCode: 400, + }, + ], + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + meta: 'instrumentation', + module: 'destination', + }, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts index fb5b689a96..c603ef6664 100644 --- a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts +++ b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts @@ -1,4 +1,9 @@ -export const data = [ +import { generateMetadata } from '../../../testUtils'; +import { V1BusinessTestScenarion } from './business'; +import { v1OauthScenarios } from './oauth'; +import { v1OtherScenarios } from './other'; + +const v0testCases = [ { name: 'criteo_audience', description: 'Test 0', @@ -38,6 +43,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(1), + destinationConfig: {}, }, method: 'POST', }, @@ -70,7 +78,7 @@ export const data = [ version: '1', type: 'REST', method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', + endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken', headers: { Authorization: 'Bearer success_access_token', 'Content-Type': 'application/json', @@ -96,6 +104,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(2), + destinationConfig: {}, }, method: 'POST', }, @@ -123,8 +134,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'aborted', feature: 'dataDelivery', implementation: 'native', @@ -147,7 +158,7 @@ export const data = [ version: '1', type: 'REST', method: 'PATCH', - endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', + endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken', headers: { Authorization: 'Bearer success_access_token', 'Content-Type': 'application/json', @@ -173,6 +184,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(3), + destinationConfig: {}, }, method: 'POST', }, @@ -200,8 +214,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'aborted', feature: 'dataDelivery', implementation: 'native', @@ -250,6 +264,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(4), + destinationConfig: {}, }, method: 'POST', }, @@ -275,8 +292,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'aborted', feature: 'dataDelivery', implementation: 'native', @@ -327,6 +344,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(5), + destinationConfig: {}, }, method: 'POST', }, @@ -352,8 +372,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', feature: 'dataDelivery', implementation: 'native', errorType: 'retryable', @@ -403,6 +423,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(6), + destinationConfig: {}, }, method: 'POST', }, @@ -421,8 +444,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'throttled', feature: 'dataDelivery', implementation: 'native', @@ -472,6 +495,9 @@ export const data = [ params: { destination: 'criteo_audience', }, + userId: '1234', + metadata: generateMetadata(7), + destinationConfig: {}, }, method: 'POST', }, @@ -492,8 +518,8 @@ export const data = [ statTags: { destType: 'CRITEO_AUDIENCE', errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', errorType: 'aborted', feature: 'dataDelivery', implementation: 'native', @@ -506,3 +532,10 @@ export const data = [ }, }, ]; + +export const data = [ + ...v0testCases, + ...V1BusinessTestScenarion, + ...v1OauthScenarios, + ...v1OtherScenarios, +]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts b/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts new file mode 100644 index 0000000000..982397f7c3 --- /dev/null +++ b/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts @@ -0,0 +1,133 @@ +import { params, headers } from './business'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; + +const commonStatTags = { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; + +export const v1OauthScenarios = [ + { + id: 'criteo_audience_oauth_0', + name: 'criteo_audience', + description: '[OAUTH]:: Test expired access token', + successCriteria: 'Should return a 401 status code with authErrorCategory as REFRESH_TOKEN', + scenario: 'oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params, + headers, + method: 'PATCH', + endpoint: + 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + status: 401, + authErrorCategory: 'REFRESH_TOKEN', + response: [ + { + error: + 'The authorization token has expired during criteo_audience response transformation', + metadata: generateMetadata(1), + statusCode: 401, + }, + ], + message: + 'The authorization token has expired during criteo_audience response transformation', + statTags: commonStatTags, + }, + }, + }, + }, + }, + { + id: 'criteo_audience_oauth_1', + name: 'criteo_audience', + description: '[OAUTH]:: Test invalid access token', + successCriteria: + 'We should get a 401 status code with errorCode authorization-token-invalid. As we need to refresh the token for these conditions, authErrorCategory should be REFRESH_TOKEN', + scenario: 'oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + params, + headers, + method: 'PATCH', + endpoint: + 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken', + }, + [generateMetadata(2)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 401, + body: { + output: { + status: 401, + authErrorCategory: 'REFRESH_TOKEN', + response: [ + { + error: + 'The authorization header is invalid during criteo_audience response transformation', + metadata: generateMetadata(2), + statusCode: 401, + }, + ], + statTags: commonStatTags, + message: + 'The authorization header is invalid during criteo_audience response transformation', + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/other.ts b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts new file mode 100644 index 0000000000..f3a0688f88 --- /dev/null +++ b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts @@ -0,0 +1,196 @@ +import { params, headers } from './business'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; + +export const v1OtherScenarios = [ + { + id: 'criteo_audience_other_0', + name: 'criteo_audience', + description: '[Other]:: Test for checking service unavailable scenario', + successCriteria: 'Should return a 500 status code with', + scenario: 'other', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + method: 'PATCH', + endpoint: 'https://random_test_url/test_for_internal_server_error', + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + response: [ + { + error: '""', + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + message: 'Request Failed: during criteo_audience response transformation (Retryable)', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + feature: 'dataDelivery', + implementation: 'native', + errorType: 'retryable', + module: 'destination', + }, + }, + }, + }, + }, + }, + { + id: 'criteo_audience_other_1', + name: 'criteo_audience', + description: '[Other]:: Test for checking throttling scenario', + successCriteria: 'Should return a 429 status code', + scenario: 'other', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + method: 'PATCH', + endpoint: 'https://random_test_url/test_for_too_many_requests', + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + }, + [generateMetadata(2)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 429, + response: [ + { + error: '{}', + metadata: generateMetadata(2), + statusCode: 429, + }, + ], + message: + 'Request Failed: during criteo_audience response transformation - due to Request Limit exceeded, (Throttled)', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + feature: 'dataDelivery', + implementation: 'native', + errorType: 'throttled', + module: 'destination', + }, + }, + }, + }, + }, + }, + { + id: 'criteo_audience_other_2', + name: 'criteo_audience', + description: '[Other]:: Test for checking unknown error scenario', + successCriteria: 'Should return a 410 status code and abort the event', + scenario: 'other', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + method: 'PATCH', + JSON: { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, + }, + endpoint: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', + }, + [generateMetadata(3)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + response: [ + { + error: '{"message":"unknown error"}', + metadata: generateMetadata(3), + statusCode: 400, + }, + ], + message: + 'Request Failed: during criteo_audience response transformation with status "410" due to "{"message":"unknown error"}", (Aborted) ', + statTags: { + destType: 'CRITEO_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + }, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/criteo_audience/network.ts b/test/integrations/destinations/criteo_audience/network.ts index 959e8a2112..7ccf649e2a 100644 --- a/test/integrations/destinations/criteo_audience/network.ts +++ b/test/integrations/destinations/criteo_audience/network.ts @@ -1,3 +1,23 @@ +const headers = { + Authorization: 'Bearer success_access_token', + 'Content-Type': 'application/json', + Accept: 'application/json', + 'User-Agent': 'RudderLabs', +}; +const params = { destination: 'criteo_audience' }; +const method = 'PATCH'; +const commonData = { + data: { + type: 'ContactlistAmendment', + attributes: { + operation: 'add', + identifierType: 'madid', + identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + internalIdentifiers: false, + }, + }, +}; + export const networkCallsData = [ { httpReq: { @@ -14,117 +34,74 @@ export const networkCallsData = [ }, }, }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + params, + headers, + method, }, httpRes: { status: 200 }, }, { httpReq: { - url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist', + url: 'https://api.criteo.com/2022-10/audiences/34894/contactlist', data: { data: { type: 'ContactlistAmendment', attributes: { operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], + identifierType: 'email', internalIdentifiers: false, + identifiers: [ + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + 'alex@email.com', + 'amy@email.com', + 'van@email.com', + ], }, }, }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', - }, - httpRes: { - code: '400', - data: { - errors: [ - { - traceIdentifier: '80a1a0ba3981b04da847d05700752c77', - type: 'authorization', - code: 'authorization-token-expired', - instance: '/2022-10/audiences/123/contactlist', - title: 'The authorization token has expired', - }, - ], - }, - status: 401, + params, + headers, + method, }, + httpRes: { status: 200 }, }, { httpReq: { - url: 'https://api.criteo.com/2022-10/audiences/34895/contactlist', + url: 'https://api.criteo.com/2022-10/audiences/34893/contactlist', data: { data: { type: 'ContactlistAmendment', attributes: { - operation: 'add', + operation: 'remove', identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], internalIdentifiers: false, + identifiers: [ + 'sample_madid', + 'sample_madid_1', + 'sample_madid_2', + 'sample_madid_10', + 'sample_madid_13', + 'sample_madid_11', + 'sample_madid_12', + ], }, }, }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', - }, - httpRes: { - code: '400', - data: { - errors: [ - { - traceIdentifier: '80a1a0ba3981b04da847d05700752c77', - type: 'authorization', - code: 'authorization-token-invalid', - instance: '/2022-10/audiences/123/contactlist', - title: 'The authorization header is invalid', - }, - ], - }, - status: 401, + params, + headers, + method, }, + httpRes: { status: 200 }, }, { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34896/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '400', @@ -143,25 +120,10 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34897/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '500', @@ -180,50 +142,20 @@ export const networkCallsData = [ { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34898/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '429', data: {}, status: 429 }, }, { httpReq: { url: 'https://api.criteo.com/2022-10/audiences/34899/contactlist', - data: { - data: { - type: 'ContactlistAmendment', - attributes: { - operation: 'add', - identifierType: 'madid', - identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'], - internalIdentifiers: false, - }, - }, - }, - params: { destination: 'criteo_audience' }, - headers: { - Authorization: 'Bearer success_access_token', - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'PATCH', + data: commonData, + params, + headers, + method, }, httpRes: { code: '400', data: { message: 'unknown error' }, status: 410 }, }, From c7133b32815dc3cf8d4172322f6d160e57ba794e Mon Sep 17 00:00:00 2001 From: chandumlg <54652834+chandumlg@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:58:42 -0600 Subject: [PATCH 17/37] chore: enable batch response schema check (#3083) --- test/integrations/testUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index 683f9dbe3b..8e26c404db 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -484,9 +484,9 @@ export const validateTestWithZOD = (testPayload: TestCaseData, response: any) => case 'router': RouterTransformationResponseListSchema.parse(response.body.output); break; - // case 'batch': - // BatchScheam.parse(responseBody); - // break; + case 'batch': + RouterTransformationResponseListSchema.parse(response.body); + break; // case 'user_deletion': // DeletionSchema.parse(responseBody); // break; From a8b8f23d30b11bfe50cf819a31c8fcf3e196d950 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Thu, 15 Feb 2024 11:38:41 +0530 Subject: [PATCH 18/37] chore: braze proxy v1 test (#3087) * chore: refactor braze proxy v1 tests * chore: address review comments and cleanup * chore: cleanup of mock --------- Co-authored-by: Utsab Chowdhury --- .../braze/dataDelivery/business.ts | 377 ++++++++++++++++++ .../destinations/braze/dataDelivery/data.ts | 6 +- .../destinations/braze/dataDelivery/other.ts | 204 ++++++++++ .../destinations/braze/network.ts | 102 ++++- test/integrations/testUtils.ts | 4 +- 5 files changed, 690 insertions(+), 3 deletions(-) create mode 100644 test/integrations/destinations/braze/dataDelivery/business.ts create mode 100644 test/integrations/destinations/braze/dataDelivery/other.ts diff --git a/test/integrations/destinations/braze/dataDelivery/business.ts b/test/integrations/destinations/braze/dataDelivery/business.ts new file mode 100644 index 0000000000..4997c5ffae --- /dev/null +++ b/test/integrations/destinations/braze/dataDelivery/business.ts @@ -0,0 +1,377 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; + +const BRAZE_USERS_TRACK_ENDPOINT = 'https://rest.iad-03.braze.com/users/track'; + +const partner = 'RudderStack'; + +const headers = { + Accept: 'application/json', + Authorization: 'Bearer api_key', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', +}; + +const BrazeEvent1 = { + name: 'Product List Viewed', + time: '2023-11-30T21:48:45.634Z', + properties: { + products: [ + { + sku: '23-04-52-62-01-18', + name: 'Broman Hoodie', + price: '97.99', + variant: [ + { + id: 39653520310368, + sku: '23-04-52-62-01-18', + grams: 0, + price: '97.99', + title: '(SM)', + weight: 0, + option1: '(SM)', + taxable: true, + position: 1, + tax_code: '', + created_at: '2023-05-18T12:56:22-06:00', + product_id: 6660780884064, + updated_at: '2023-11-30T15:48:43-06:00', + weight_unit: 'kg', + quantity_rule: { + min: 1, + increment: 1, + }, + compare_at_price: '139.99', + inventory_policy: 'deny', + requires_shipping: true, + inventory_quantity: 8, + fulfillment_service: 'manual', + inventory_management: 'shopify', + quantity_price_breaks: [], + old_inventory_quantity: 8, + }, + ], + category: '62 OTHER/RETRO', + currency: 'CAD', + product_id: 6660780884064, + }, + { + sku: '23-04-08-61-01-18', + name: 'Kipling Camo Hoodie', + price: '69.99', + variant: [ + { + id: 39672628740192, + sku: '23-04-08-61-01-18', + grams: 0, + price: '69.99', + title: '(SM)', + weight: 0, + option1: '(SM)', + taxable: true, + position: 1, + tax_code: '', + created_at: '2023-06-28T12:52:56-06:00', + product_id: 6666835853408, + updated_at: '2023-11-30T15:48:43-06:00', + weight_unit: 'kg', + quantity_rule: { + min: 1, + increment: 1, + }, + compare_at_price: '99.99', + inventory_policy: 'deny', + requires_shipping: true, + inventory_quantity: 8, + fulfillment_service: 'manual', + inventory_management: 'shopify', + quantity_price_breaks: [], + old_inventory_quantity: 8, + }, + ], + category: 'Misc', + currency: 'CAD', + product_id: 6666835853408, + }, + ], + }, + _update_existing_only: false, + user_alias: { + alias_name: 'ab7de609-9bec-8e1c-42cd-084a1cd93a4e', + alias_label: 'rudder_id', + }, +}; + +const BrazeEvent2 = { + name: 'Add to Cart', + time: '2020-01-24T11:59:02.403+05:30', + properties: { + revenue: 50, + }, + external_id: 'mickeyMouse', +}; + +const BrazePurchaseEvent = { + product_id: '507f1f77bcf86cd799439011', + price: 0, + currency: 'USD', + quantity: 1, + time: '2020-01-24T11:59:02.402+05:30', + _update_existing_only: false, + user_alias: { + alias_name: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca', + alias_label: 'rudder_id', + }, +}; + +const metadataArray = [generateMetadata(1), generateMetadata(2), generateMetadata(3)]; + +const errorMessages = { + message_1: '{"events_processed":2,"purchases_processed":1,"message":"success"}', + message_2: + '{"events_processed":1,"message":"success","errors":[{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":1},{"type":"\'quantity\' is not valid","input_array":"purchases","index":0}]}', + message_3: + '{"message":"Valid data must be provided in the \'attributes\', \'events\', or \'purchases\' fields.","errors":[{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":0},{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":1},{"type":"\'quantity\' is not valid","input_array":"purchases","index":0}]}', +}; + +const expectedStatTags = { + errorCategory: 'network', + errorType: 'aborted', + destType: 'BRAZE', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'braze_v1_scenario_1', + name: 'braze', + description: + '[Proxy v1 API] :: Test for a valid request - 2 events and 1 purchase event are sent where the destination responds with 200 without any error', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [BrazeEvent1, BrazeEvent2], + purchases: [BrazePurchaseEvent], + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/valid_scenario1`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: errorMessages.message_1, + statusCode: 200, + metadata: generateMetadata(1), + }, + { + error: errorMessages.message_1, + statusCode: 200, + metadata: generateMetadata(2), + }, + { + error: errorMessages.message_1, + statusCode: 200, + metadata: generateMetadata(3), + }, + ], + status: 200, + message: 'Request for braze Processed Successfully', + }, + }, + }, + }, + }, + { + id: 'braze_v1_scenario_2', + name: 'braze', + description: + '[Proxy v1 API] :: Test for a invalid request - 2 events and 1 purchase event are sent where the destination responds with 200 with error for a one of the event and the purchase event', + successCriteria: 'Should return 200 with error for one of the event and the purchase event', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [{ ...BrazeEvent1, user_alias: undefined }, BrazeEvent2], // modifying first event to be invalid + purchases: [{ ...BrazePurchaseEvent, quantity: 'invalid quantity' }], // modifying purchase event to be invalid + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario1`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: errorMessages.message_2, + statusCode: 200, + metadata: generateMetadata(1), + }, + { + error: errorMessages.message_2, + statusCode: 200, + metadata: generateMetadata(2), + }, + { + error: errorMessages.message_2, + statusCode: 200, + metadata: generateMetadata(3), + }, + ], + status: 200, + message: 'Request for braze Processed Successfully', + }, + }, + }, + }, + }, + { + id: 'braze_v1_scenario_3', + name: 'braze', + description: '[Proxy v1 API] :: Test for an invalid request - all the payloads are invalid', + successCriteria: 'Should return 400 with error for all the payloads', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [ + { ...BrazeEvent1, user_alias: undefined }, + { ...BrazeEvent2, external_id: undefined }, + ], // modifying first event to be invalid + purchases: [{ ...BrazePurchaseEvent, quantity: 'invalid quantity' }], // modifying purchase event to be invalid + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario2`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: errorMessages.message_3, + statusCode: 400, + metadata: generateMetadata(1), + }, + { + error: errorMessages.message_3, + statusCode: 400, + metadata: generateMetadata(2), + }, + { + error: errorMessages.message_3, + statusCode: 400, + metadata: generateMetadata(3), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 400', + status: 400, + }, + }, + }, + }, + }, + { + id: 'braze_v1_scenario_4', + name: 'braze', + description: '[Proxy v1 API] :: Test for invalid auth scneario', + successCriteria: 'Should return 400 for all the payloads', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + JSON: { + partner, + events: [BrazeEvent1, BrazeEvent2], + purchases: [BrazePurchaseEvent], + }, + headers, + endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario3`, + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '{"message":"Invalid API Key"}', + statusCode: 401, + metadata: generateMetadata(1), + }, + { + error: '{"message":"Invalid API Key"}', + statusCode: 401, + metadata: generateMetadata(2), + }, + { + error: '{"message":"Invalid API Key"}', + statusCode: 401, + metadata: generateMetadata(3), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 401', + status: 401, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/braze/dataDelivery/data.ts b/test/integrations/destinations/braze/dataDelivery/data.ts index 3c1a97811e..2596a4b959 100644 --- a/test/integrations/destinations/braze/dataDelivery/data.ts +++ b/test/integrations/destinations/braze/dataDelivery/data.ts @@ -1,6 +1,8 @@ import MockAdapter from 'axios-mock-adapter'; +import { testScenariosForV1API } from './business'; +import { otherScenariosV1 } from './other'; -export const data = [ +export const existingTestData = [ { name: 'braze', description: 'Test 0', @@ -846,3 +848,5 @@ export const data = [ }, }, ]; + +export const data = [...existingTestData, ...testScenariosForV1API, ...otherScenariosV1]; diff --git a/test/integrations/destinations/braze/dataDelivery/other.ts b/test/integrations/destinations/braze/dataDelivery/other.ts new file mode 100644 index 0000000000..9353899a65 --- /dev/null +++ b/test/integrations/destinations/braze/dataDelivery/other.ts @@ -0,0 +1,204 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; + +const expectedStatTags = { + errorCategory: 'network', + errorType: 'retryable', + destType: 'BRAZE', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', +}; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'braze_v1_other_scenario_1', + name: 'braze', + description: + '[Proxy v1 API] :: Scenario for testing Service Unavailable error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_service_not_available', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: + '{"error":{"message":"Service Unavailable","description":"The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later."}}', + statusCode: 503, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 503', + status: 503, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_2', + name: 'braze', + description: '[Proxy v1 API] :: Scenario for testing Internal Server error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_internal_server_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Internal Server Error"', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_3', + name: 'braze', + description: '[Proxy v1 API] :: Scenario for testing Gateway Time Out error from destination', + successCriteria: 'Should return 504 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_gateway_time_out', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Gateway Timeout"', + statusCode: 504, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 504', + status: 504, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_4', + name: 'braze', + description: '[Proxy v1 API] :: Scenario for testing null response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_response', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 500', + status: 500, + }, + }, + }, + }, + }, + { + id: 'braze_v1_other_scenario_5', + name: 'braze', + description: + '[Proxy v1 API] :: Scenario for testing null and no status response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_and_no_status', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + message: 'Request failed for braze with status: 500', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/braze/network.ts b/test/integrations/destinations/braze/network.ts index 40d75c9d34..ae093ce1f4 100644 --- a/test/integrations/destinations/braze/network.ts +++ b/test/integrations/destinations/braze/network.ts @@ -524,4 +524,104 @@ const deleteNwData = [ }, }, ]; -export const networkCallsData = [...deleteNwData, ...dataDeliveryMocksData]; + +const BRAZE_USERS_TRACK_ENDPOINT = 'https://rest.iad-03.braze.com/users/track'; + +// New Mocks for Braze +const updatedDataDeliveryMocksData = [ + { + description: + 'Mock response from destination depicting a valid request for 2 valid events and 1 purchase event', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/valid_scenario1`, + method: 'POST', + }, + httpRes: { + data: { + events_processed: 2, + purchases_processed: 1, + message: 'success', + }, + status: 200, + }, + }, + + { + description: + 'Mock response from destination depicting a request with 1 valid and 1 invalid event and 1 invalid purchase event', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario1`, + method: 'POST', + }, + httpRes: { + data: { + events_processed: 1, + message: 'success', + errors: [ + { + type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required", + input_array: 'events', + index: 1, + }, + { + type: "'quantity' is not valid", + input_array: 'purchases', + index: 0, + }, + ], + }, + status: 200, + }, + }, + + { + description: + 'Mock response from destination depicting a request with all the payloads are invalid', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario2`, + method: 'POST', + }, + httpRes: { + data: { + message: + "Valid data must be provided in the 'attributes', 'events', or 'purchases' fields.", + errors: [ + { + type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required", + input_array: 'events', + index: 0, + }, + { + type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required", + input_array: 'events', + index: 1, + }, + { + type: "'quantity' is not valid", + input_array: 'purchases', + index: 0, + }, + ], + }, + status: 400, + }, + }, + { + description: 'Mock response from destination depicting a request with invalid credentials', + httpReq: { + url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario3`, + method: 'POST', + }, + httpRes: { + data: { + message: 'Invalid API Key', + }, + status: 401, + }, + }, +]; +export const networkCallsData = [ + ...deleteNwData, + ...dataDeliveryMocksData, + ...updatedDataDeliveryMocksData, +]; diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index 8e26c404db..07d5e5eb83 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -527,7 +527,9 @@ export const generateMetadata = (jobId: number): any => { sourceId: 'default-sourceId', destinationId: 'default-destinationId', workspaceId: 'default-workspaceId', - secret: {}, + secret: { + accessToken: 'default-accessToken', + }, dontBatch: false, }; }; From b29d624abf5f4d6267180a9a4b6b93c8feaace3e Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Mon, 19 Feb 2024 08:51:54 +0530 Subject: [PATCH 19/37] chore: resolve conflicts --- .../common.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts b/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts index 3af7791ec8..9b79a7bcbd 100644 --- a/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts +++ b/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts @@ -1,13 +1,25 @@ +import { Destination } from '../../../../src/types'; + const destType = 'the_trade_desk_real_time_conversions'; const destTypeInUpperCase = 'THE_TRADE_DESK_REAL_TIME_CONVERSIONS'; const advertiserId = 'test-advertiser-id'; const trackerId = 'test-trackerId'; -const sampleDestination = { +const sampleDestination: Destination = { Config: { advertiserId, trackerId, }, - DestinationDefinition: { Config: { cdkV2Enabled: true } }, + Enabled: true, + ID: '123', + Name: 'TRADE_DESK_REAL_TIME_CONVERSIONS', + WorkspaceID: 'test-workspace-id', + Transformations: [], + DestinationDefinition: { + ID: '123', + DisplayName: 'Trade Desk', + Name: 'TRADE_DESK', + Config: { cdkV2Enabled: true }, + }, }; const sampleContextForConversion = { From 28752cb76ffc13a70c4be3f16327a2468af84c1f Mon Sep 17 00:00:00 2001 From: ItsSudip Date: Tue, 20 Feb 2024 17:16:32 +0530 Subject: [PATCH 20/37] chore: updated test cases according to new flow for tiktok_ads --- .../criteo_audience/dataDelivery/other.ts | 3 +- .../tiktok_ads/dataDelivery/business.ts | 249 ++++++++++++++++++ .../tiktok_ads/dataDelivery/data.ts | 6 +- .../tiktok_ads/dataDelivery/other.ts | 175 ++++++++++++ 4 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 test/integrations/destinations/tiktok_ads/dataDelivery/business.ts create mode 100644 test/integrations/destinations/tiktok_ads/dataDelivery/other.ts diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/other.ts b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts index f3a0688f88..145be62528 100644 --- a/test/integrations/destinations/criteo_audience/dataDelivery/other.ts +++ b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts @@ -1,7 +1,8 @@ +import { ProxyV1TestData } from '../../../testTypes'; import { params, headers } from './business'; import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; -export const v1OtherScenarios = [ +export const v1OtherScenarios: ProxyV1TestData[] = [ { id: 'criteo_audience_other_0', name: 'criteo_audience', diff --git a/test/integrations/destinations/tiktok_ads/dataDelivery/business.ts b/test/integrations/destinations/tiktok_ads/dataDelivery/business.ts new file mode 100644 index 0000000000..895188fa3f --- /dev/null +++ b/test/integrations/destinations/tiktok_ads/dataDelivery/business.ts @@ -0,0 +1,249 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; + +export const commonHeaderPart = { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', +}; + +export const params = { + destination: 'tiktok_ads', +}; + +export const statTags = { + destType: 'TIKTOK_ADS', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; + +export const commonParts = { + context: { + ad: { + callback: '123ATXSfe', + }, + page: { + url: 'http://demo.mywebsite.com/purchase', + referrer: 'http://demo.mywebsite.com', + }, + user: { + external_id: 'f0e388f53921a51f0bb0fc8a2944109ec188b59172935d8f23020b1614cc44bc', + phone_number: '2f9d2b4df907e5c9a7b3434351b55700167b998a83dc479b825096486ffcf4ea', + email: 'dd6ff77f54e2106661089bae4d40cdb600979bf7edc9eb65c0942ba55c7c2d7f', + }, + ip: '13.57.97.131', + user_agent: 'Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion', + }, + pixel_code: 'A1T8T4UYGVIQA8ORZMX9', + partner_name: 'RudderStack', + event: 'CompletePayment', + event_id: '1616318632825_357', + timestamp: '2020-09-17T19:49:27Z', +}; + +export const V1BusinessTestScenarion: ProxyV1TestData[] = [ + { + id: 'tiktok_ads_business_0', + name: 'tiktok_ads', + description: '[Business]:: Test for tiktok_ads with multiple contents in properties', + feature: 'dataDelivery', + scenario: 'business', + successCriteria: 'Should return 200 after successfully sending the request', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: { + ...commonHeaderPart, + 'test-dest-response-key': 'successResponse', + }, + params, + endpoint: 'https://business-api.tiktok.com/open_api/v1.2/pixel/batch/', + JSON: { + ...commonParts, + properties: { + contents: [ + { + price: 8, + quantity: 2, + content_type: 'socks', + content_id: '1077218', + }, + { + price: 30, + quantity: 1, + content_type: 'dress', + content_id: '1197218', + }, + ], + currency: 'USD', + value: 46, + }, + }, + }, + [generateMetadata(1234)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: '[TIKTOK_ADS Response Handler] - Request Processed Successfully', + response: [ + { + error: '{"code":0,"message":"OK"}', + statusCode: 200, + metadata: generateMetadata(1234), + }, + ], + }, + }, + }, + }, + }, + { + id: 'tiktok_ads_business_1', + name: 'tiktok_ads', + description: + '[Business]:: Test for tiktok_ads with multiple contents in properties but content_id is not a string', + feature: 'dataDelivery', + scenario: 'business', + successCriteria: 'Should return 400 after successfully processing the request with code 40002', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + params, + headers: { + ...commonHeaderPart, + 'test-dest-response-key': 'invalidDataTypeResponse', + }, + endpoint: 'https://business-api.tiktok.com/open_api/v1.2/pixel/batch/', + JSON: { + properties: { + contents: [ + { + price: 8, + quantity: 2, + content_type: 'socks', + content_id: 1077218, + }, + { + price: 30, + quantity: 1, + content_type: 'dress', + content_id: 1197218, + }, + ], + currency: 'USD', + value: 46, + }, + ...commonParts, + }, + }, + [generateMetadata(1234)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Request failed with status: 40002', + response: [ + { + statusCode: 400, + error: + '{"code":40002,"message":"Batch.0.properties.contents.0.content_id: Not a valid string"}', + metadata: generateMetadata(1234), + }, + ], + statTags, + }, + }, + }, + }, + }, + { + id: 'tiktok_ads_business_2', + name: 'tiktok_ads', + description: '[Business]:: Test for tiktok_ads with wrong pixel code', + feature: 'dataDelivery', + scenario: 'business', + successCriteria: 'Should return 400 after successfully processing the request with code 40001', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + params, + endpoint: 'https://business-api.tiktok.com/open_api/v1.2/pixel/batch/', + headers: { + ...commonHeaderPart, + 'test-dest-response-key': 'invalidPermissionsResponse', + }, + JSON: { + ...commonParts, + properties: { + contents: [ + { + price: 8, + quantity: 2, + content_type: 'socks', + content_id: 1077218, + }, + { + price: 30, + quantity: 1, + content_type: 'dress', + content_id: 1197218, + }, + ], + currency: 'USD', + value: 46, + }, + }, + }, + [generateMetadata(1234)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Request failed with status: 40001', + response: [ + { + statusCode: 400, + error: + '{"code":40001,"message":"No permission to operate pixel code: BU35TSQHT2A1QT375OMG. You must be an admin or operator of this advertiser account."}', + metadata: generateMetadata(1234), + }, + ], + statTags, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/tiktok_ads/dataDelivery/data.ts b/test/integrations/destinations/tiktok_ads/dataDelivery/data.ts index 810e1de475..399fd26649 100644 --- a/test/integrations/destinations/tiktok_ads/dataDelivery/data.ts +++ b/test/integrations/destinations/tiktok_ads/dataDelivery/data.ts @@ -1,8 +1,10 @@ import { AxiosError } from 'axios'; import MockAxiosAdapter from 'axios-mock-adapter'; import lodash from 'lodash'; +import { V1BusinessTestScenarion } from './business'; +import { v1OtherScenarios } from './other'; -export const data = [ +const oldV0TestCases = [ { name: 'tiktok_ads', description: 'Test 0', @@ -670,3 +672,5 @@ export const data = [ }, }, ]; + +export const data = [...oldV0TestCases, ...V1BusinessTestScenarion, ...v1OtherScenarios]; diff --git a/test/integrations/destinations/tiktok_ads/dataDelivery/other.ts b/test/integrations/destinations/tiktok_ads/dataDelivery/other.ts new file mode 100644 index 0000000000..0675ebcd05 --- /dev/null +++ b/test/integrations/destinations/tiktok_ads/dataDelivery/other.ts @@ -0,0 +1,175 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { commonHeaderPart, params, statTags, commonParts } from './business'; + +const commonProperties = { + contents: [ + { + price: 8, + quantity: 2, + content_type: 'socks', + content_id: 1077218, + }, + { + price: 30, + quantity: 1, + content_type: 'dress', + content_id: 1197218, + }, + ], + currency: 'USD', + value: 46, +}; + +export const v1OtherScenarios: ProxyV1TestData[] = [ + { + id: 'tiktok_ads_other_0', + name: 'tiktok_ads', + description: '[Other]:: Test for tiktok_ads when rate limit is reached', + feature: 'dataDelivery', + scenario: 'other', + successCriteria: 'Should return 429 after successfully sending the request', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + params, + endpoint: 'https://business-api.tiktok.com/open_api/v1.2/pixel/batch/', + headers: { ...commonHeaderPart, 'test-dest-response-key': 'tooManyRequests' }, + JSON: { + ...commonParts, + properties: commonProperties, + }, + }, + [generateMetadata(1234)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 429, + message: 'Request failed with status: 40100', + response: [ + { + error: '{"code":40100,"message":"Too many requests. Please retry in some time."}', + statusCode: 429, + metadata: generateMetadata(1234), + }, + ], + statTags: { + ...statTags, + errorType: 'throttled', + }, + }, + }, + }, + }, + }, + { + id: 'tiktok_ads_other_1', + name: 'tiktok_ads', + description: '[Other]:: Test for tiktok_ads when request failed due to bad gateway', + feature: 'dataDelivery', + scenario: 'other', + successCriteria: 'Should return 500 status code after successfully sending the request', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + params, + endpoint: 'https://business-api.tiktok.com/open_api/v1.2/pixel/batch/', + headers: { ...commonHeaderPart, 'test-dest-response-key': '502-BadGateway' }, + JSON: { + ...commonParts, + properties: commonProperties, + }, + }, + [generateMetadata(1234)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 502, + message: 'Request failed with status: 502', + response: [ + { + error: + '"\\r\\n502 Bad Gateway\\r\\n\\r\\n

502 Bad Gateway

\\r\\n
nginx
\\r\\n\\r\\n\\r\\n"', + statusCode: 502, + metadata: generateMetadata(1234), + }, + ], + statTags: { + ...statTags, + errorType: 'retryable', + }, + }, + }, + }, + }, + }, + { + id: 'tiktok_ads_other_2', + name: 'tiktok_ads', + description: + '[Other]:: Test for tiktok_ads when request failed due to unavailability of service', + feature: 'dataDelivery', + scenario: 'other', + successCriteria: 'Should return 500 status code after successfully sending the request', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers: commonHeaderPart, + params, + endpoint: 'https://random_test_url/test_for_service_not_available', + JSON: { + ...commonParts, + properties: commonProperties, + }, + }, + [generateMetadata(1234)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 503, + message: 'Request failed with status: 503', + 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(1234), + }, + ], + statTags: { + ...statTags, + errorType: 'retryable', + }, + }, + }, + }, + }, + }, +]; From abbdca07374773952331c1674489db0c9c53eca4 Mon Sep 17 00:00:00 2001 From: ItsSudip Date: Sat, 24 Feb 2024 23:49:31 +0530 Subject: [PATCH 21/37] chore: updated test cases according to new flow for clevertap --- .../clevertap/dataDelivery/business.ts | 219 ++++++++++++++++++ .../clevertap/dataDelivery/data.ts | 5 +- 2 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 test/integrations/destinations/clevertap/dataDelivery/business.ts diff --git a/test/integrations/destinations/clevertap/dataDelivery/business.ts b/test/integrations/destinations/clevertap/dataDelivery/business.ts new file mode 100644 index 0000000000..edab4ee6d7 --- /dev/null +++ b/test/integrations/destinations/clevertap/dataDelivery/business.ts @@ -0,0 +1,219 @@ +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', +}; +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: { + destType: 'CLEVERTAP', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, + { + 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: { + destType: 'CLEVERTAP', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', + }, + }, + }, + }, + }, + }, +]; 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]; From e08b826e8547e44284927dd542b822f5578d0959 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 4 Mar 2024 13:13:50 +0000 Subject: [PATCH 22/37] chore(release): 1.58.0 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a043cfb6e3..c143851091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.58.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.57.1...v1.58.0) (2024-03-04) + + +### Features + +* add support for interaction events in sfmc ([#3109](https://github.com/rudderlabs/rudder-transformer/issues/3109)) ([0486049](https://github.com/rudderlabs/rudder-transformer/commit/0486049ba2ad96b50d8f29e96b46b96a8a5c9f76)) +* add support of custom page/screen event name in mixpanel ([#3098](https://github.com/rudderlabs/rudder-transformer/issues/3098)) ([0eb2393](https://github.com/rudderlabs/rudder-transformer/commit/0eb2393939fba2452ef7f07a1d149d87f18290c3)) +* consent mode support for google adwords remarketing list ([#3143](https://github.com/rudderlabs/rudder-transformer/issues/3143)) ([7532c90](https://github.com/rudderlabs/rudder-transformer/commit/7532c90d7e1feac00f12961c56da18757010f44a)) +* **facebook:** update content_type mapping logic for fb pixel and fb conversions ([#3113](https://github.com/rudderlabs/rudder-transformer/issues/3113)) ([aea417c](https://github.com/rudderlabs/rudder-transformer/commit/aea417cd2691547399010c034cadbc5db6b0c6ee)) +* klaviyo profile mapping ([#3105](https://github.com/rudderlabs/rudder-transformer/issues/3105)) ([2761786](https://github.com/rudderlabs/rudder-transformer/commit/2761786ff3fc99ed6d4d3b7a6c2400226b1cfb12)) +* onboard new destination ninetailed ([#3106](https://github.com/rudderlabs/rudder-transformer/issues/3106)) ([0e2588e](https://github.com/rudderlabs/rudder-transformer/commit/0e2588ecd87f3b2c6877a099aa1cbf2d5325966c)) + + +### Bug Fixes + +* add error handling for tiktok ads ([#3144](https://github.com/rudderlabs/rudder-transformer/issues/3144)) ([e93e47f](https://github.com/rudderlabs/rudder-transformer/commit/e93e47f33e098104fb532916932fe38bbfeaa4a1)) +* **algolia:** added check for objectIds or filters to be non empty ([#3126](https://github.com/rudderlabs/rudder-transformer/issues/3126)) ([d619c97](https://github.com/rudderlabs/rudder-transformer/commit/d619c9769cd270cb2d16dad0865683ff4beb2d19)) +* clevertap remove stringification of array object properties ([#3048](https://github.com/rudderlabs/rudder-transformer/issues/3048)) ([69e43b6](https://github.com/rudderlabs/rudder-transformer/commit/69e43b6ffadeaec87b7440da34a341890ceba252)) +* convert to string from null in hs ([#3136](https://github.com/rudderlabs/rudder-transformer/issues/3136)) ([75e9f46](https://github.com/rudderlabs/rudder-transformer/commit/75e9f462b0ff9b9a8abab3c78dc7d147926e9e5e)) +* event fix and added utility ([#3142](https://github.com/rudderlabs/rudder-transformer/issues/3142)) ([9b705b7](https://github.com/rudderlabs/rudder-transformer/commit/9b705b71a9d3a595ea0fbf532602c3941b0a18db)) +* metadata structure correction ([#3119](https://github.com/rudderlabs/rudder-transformer/issues/3119)) ([8351b5c](https://github.com/rudderlabs/rudder-transformer/commit/8351b5cbbf81bbc14b2f884feaae4ad3ca59a39a)) +* one_signal: Encode external_id in endpoint ([#3140](https://github.com/rudderlabs/rudder-transformer/issues/3140)) ([8a20886](https://github.com/rudderlabs/rudder-transformer/commit/8a2088608d6da4b35bbb506db2fc3df1e4d41f3b)) +* rakuten: sync property mapping sourcekeys to rudderstack standard spec ([#3129](https://github.com/rudderlabs/rudder-transformer/issues/3129)) ([2ebff95](https://github.com/rudderlabs/rudder-transformer/commit/2ebff956ff2aa74b008a8de832a31d8774d2d47e)) +* reddit revenue mapping for floating point values ([#3118](https://github.com/rudderlabs/rudder-transformer/issues/3118)) ([41f4078](https://github.com/rudderlabs/rudder-transformer/commit/41f4078011ef54334bb9ecc11a7b2ccc8831a4aa)) +* version deprecation failure false positive ([#3104](https://github.com/rudderlabs/rudder-transformer/issues/3104)) ([657b780](https://github.com/rudderlabs/rudder-transformer/commit/657b7805eb01da25a007d978198d5debf03917fd)) + ### [1.57.1](https://github.com/rudderlabs/rudder-transformer/compare/v1.57.0...v1.57.1) (2024-03-04) diff --git a/package-lock.json b/package-lock.json index a629744cf4..700207e021 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rudder-transformer", - "version": "1.57.1", + "version": "1.58.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rudder-transformer", - "version": "1.57.1", + "version": "1.58.0", "license": "ISC", "dependencies": { "@amplitude/ua-parser-js": "0.7.24", diff --git a/package.json b/package.json index f5908bc7ff..46e7ab98ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rudder-transformer", - "version": "1.57.1", + "version": "1.58.0", "description": "", "homepage": "https://github.com/rudderlabs/rudder-transformer#readme", "bugs": { From 4653b74522cc917230c211ce1df1b57e8a607ad7 Mon Sep 17 00:00:00 2001 From: Dilip Kola Date: Mon, 4 Mar 2024 19:10:23 +0530 Subject: [PATCH 23/37] fix: am formatting issues --- src/v0/destinations/am/transform.js | 1 - src/v0/destinations/am/utils.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js index bc08315fa8..2d78479ced 100644 --- a/src/v0/destinations/am/transform.js +++ b/src/v0/destinations/am/transform.js @@ -336,7 +336,6 @@ const getDefaultResponseData = (message, rawPayload, evType, groupInfo) => { return { groups, rawPayload }; }; - const getResponseData = (evType, destination, rawPayload, message, groupInfo) => { let groups; diff --git a/src/v0/destinations/am/utils.js b/src/v0/destinations/am/utils.js index 4d4fd5dc37..190a5c1bae 100644 --- a/src/v0/destinations/am/utils.js +++ b/src/v0/destinations/am/utils.js @@ -123,7 +123,6 @@ const validateEventType = (evType) => { } }; - const userPropertiesPostProcess = (rawPayload) => { const operationList = [ '$setOnce', @@ -187,5 +186,5 @@ module.exports = { getEventId, getUnsetObj, validateEventType, - userPropertiesPostProcess + userPropertiesPostProcess, }; From 05ffe820e5c5a3b346f39c268dd49fca47568461 Mon Sep 17 00:00:00 2001 From: Dilip Kola Date: Mon, 4 Mar 2024 19:41:08 +0530 Subject: [PATCH 24/37] fix: prepare-for-staging-deploy.yml --- .github/workflows/prepare-for-staging-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prepare-for-staging-deploy.yml b/.github/workflows/prepare-for-staging-deploy.yml index e7df8c43a5..a69cf90c8c 100644 --- a/.github/workflows/prepare-for-staging-deploy.yml +++ b/.github/workflows/prepare-for-staging-deploy.yml @@ -101,7 +101,7 @@ jobs: cd rudder-devops BRANCH_NAME="shared-transformer-$TAG_NAME" echo $BRANCH_NAME - if [ `git ls-remote --heads origin $BRANCH_NAME 2>/dev/null` ] + if [ -n `git ls-remote --heads origin $BRANCH_NAME 2>/dev/null` ] then echo "Staging deployment branch already exists!" else From afb2f450ddee0522e802327dce68ac33a04c9639 Mon Sep 17 00:00:00 2001 From: Dilip Kola Date: Mon, 4 Mar 2024 22:34:03 +0530 Subject: [PATCH 25/37] fix: prepare-for-staging-deploy.yml --- .github/workflows/prepare-for-staging-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prepare-for-staging-deploy.yml b/.github/workflows/prepare-for-staging-deploy.yml index a69cf90c8c..1bd7e276f4 100644 --- a/.github/workflows/prepare-for-staging-deploy.yml +++ b/.github/workflows/prepare-for-staging-deploy.yml @@ -101,7 +101,7 @@ jobs: cd rudder-devops BRANCH_NAME="shared-transformer-$TAG_NAME" echo $BRANCH_NAME - if [ -n `git ls-remote --heads origin $BRANCH_NAME 2>/dev/null` ] + if [ -n "$(git ls-remote --heads origin $BRANCH_NAME 2>/dev/null)" ] then echo "Staging deployment branch already exists!" else From 17da0a9cd2efb7b3ae061db081c737cb38d30df2 Mon Sep 17 00:00:00 2001 From: AASHISH MALIK Date: Tue, 5 Mar 2024 11:23:15 +0530 Subject: [PATCH 26/37] fix: release fix feat, bug order (#3165) --- github-release.config.js | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 github-release.config.js diff --git a/github-release.config.js b/github-release.config.js new file mode 100644 index 0000000000..4194af4530 --- /dev/null +++ b/github-release.config.js @@ -0,0 +1,5 @@ +module.exports = { + gitRawCommitsOpts: { + merges: null, + }, + }; \ No newline at end of file diff --git a/package.json b/package.json index 46e7ab98ac..070510029b 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "commit-msg": "commitlint --edit", "prepare": "node ./scripts/skipPrepareScript.js || husky install", "release": "npx standard-version", - "release:github": "DEBUG=conventional-github-releaser npx conventional-github-releaser -p angular -v", + "release:github": "DEBUG=conventional-github-releaser npx conventional-github-releaser -p angular --config github-release.config.js", "clean:node": "modclean", "check:lint": "eslint . -f json -o reports/eslint.json || exit 0" }, From dff7eb9b8072016a16e7083c60507a9d03302f17 Mon Sep 17 00:00:00 2001 From: AASHISH MALIK Date: Tue, 5 Mar 2024 11:32:26 +0530 Subject: [PATCH 27/37] fix: release action git (#3166) --- github-release.config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/github-release.config.js b/github-release.config.js index 4194af4530..df269d8a02 100644 --- a/github-release.config.js +++ b/github-release.config.js @@ -1,5 +1,5 @@ module.exports = { - gitRawCommitsOpts: { - merges: null, - }, - }; \ No newline at end of file + gitRawCommitsOpts: { + merges: null, + }, +}; From c106590214129596b9d24b9c741d799199139ba6 Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:30:01 +0530 Subject: [PATCH 28/37] chore: add v1 proxy tests for salesforce (#3074) * feat: update proxy data type for response handler input * feat: update proxy v1 test cases * feat: update proxy tests for cm360 Added new structure for proxy test scnearios for cm360 also added zod validations as part of tests * fix: typo * Update test/integrations/destinations/campaign_manager/dataDelivery/business.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update test/integrations/destinations/campaign_manager/dataDelivery/business.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: api contract for v1 proxy * chore: clean up zod type * chore: update testutils * chore: update V0 proxy request type and zod schema * feat: adding zod validations (#3066) * feat: add type definitions for test cases * fix: update networkHandler for rakuten --------- Co-authored-by: Utsab Chowdhury * chore: update delivery test cases for criteo audience * Revert "chore: update delivery test cases for criteo audience" This reverts commit 689b0cda0aeace910e82167375045e123e365300. * chore: add initial business tests * chore: add type def for proxy v1 test * chore: fix generateMetdata func * chore: cleanup * chore: add other scenario test, refactor * chore: address commentsx1 * chore: move test to other * chore: address commentsx2 --------- Co-authored-by: Utsab Chowdhury Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Utsab Chowdhury Co-authored-by: ItsSudip --- .../salesforce/dataDelivery/business.ts | 380 ++++++++++++++++++ .../salesforce/dataDelivery/data.ts | 77 +--- .../salesforce/dataDelivery/other.ts | 106 +++++ .../destinations/salesforce/network.ts | 264 +++++++++--- 4 files changed, 710 insertions(+), 117 deletions(-) create mode 100644 test/integrations/destinations/salesforce/dataDelivery/business.ts create mode 100644 test/integrations/destinations/salesforce/dataDelivery/other.ts diff --git a/test/integrations/destinations/salesforce/dataDelivery/business.ts b/test/integrations/destinations/salesforce/dataDelivery/business.ts new file mode 100644 index 0000000000..4e98a3fc1a --- /dev/null +++ b/test/integrations/destinations/salesforce/dataDelivery/business.ts @@ -0,0 +1,380 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload } from '../../../testUtils'; + +const commonHeaders = { + Authorization: 'Bearer token', + 'Content-Type': 'application/json', +}; +const params = { destination: 'salesforce' }; + +const users = [ + { + Email: 'danis.archurav@sbermarket.ru', + Company: 'itus.ru', + LastName: 'Danis', + FirstName: 'Archurav', + LeadSource: 'App Signup', + account_type__c: 'free_trial', + }, +]; + +const statTags = { + aborted: { + destType: 'SALESFORCE', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, + retryable: { + destType: 'SALESFORCE', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, + throttled: { + destType: 'SALESFORCE', + 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 commonRequestParameters = { + headers: commonHeaders, + JSON: users[0], + params, +}; + +const externalIdSearchData = { Planning_Categories__c: 'pc', External_ID__c: 123 }; +export const externalIDSearchedData = { + headers: commonHeaders, + JSON: externalIdSearchData, + params, +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'salesforce_v1_scenario_1', + 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( + { + ...commonRequestParameters, + 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: proxyMetdata, + statusCode: 200, + }, + ], + }, + }, + }, + }, + }, + { + id: 'salesforce_v1_scenario_2', + name: 'salesforce', + description: '[Proxy v1 API] :: Test with session expired scenario', + successCriteria: 'Should return 5XX with error Session expired or invalid, INVALID_SESSION_ID', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: + 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/invalid_session_id', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + message: + 'Salesforce Request Failed - due to "Session expired or invalid", (Retryable) during Salesforce Response Handling', + response: [ + { + error: + '[{"message":"Session expired or invalid","errorCode":"INVALID_SESSION_ID"}]', + metadata: proxyMetdata, + statusCode: 500, + }, + ], + statTags: statTags.retryable, + }, + }, + }, + }, + }, + { + id: 'salesforce_v1_scenario_3', + name: 'salesforce', + description: '[Proxy v1 API] :: Test for Invalid Auth token passed in header', + successCriteria: 'Should return 401 INVALID_AUTH_HEADER', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/2', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + 'Salesforce Request Failed: "401" due to "INVALID_HEADER_TYPE", (Aborted) during Salesforce Response Handling', + response: [ + { + error: '[{"message":"INVALID_HEADER_TYPE","errorCode":"INVALID_AUTH_HEADER"}]', + metadata: proxyMetdata, + statusCode: 400, + }, + ], + statTags: statTags.aborted, + }, + }, + }, + }, + }, + { + id: 'salesforce_v1_scenario_4', + name: 'salesforce', + description: '[Proxy v1 API] :: Test for rate limit exceeded scenario', + successCriteria: 'Should return 429 with error message "Request limit exceeded"', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/4', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + 'Salesforce Request Failed - due to "REQUEST_LIMIT_EXCEEDED", (Throttled) during Salesforce Response Handling', + response: [ + { + error: + '[{"message":"Request limit exceeded","errorCode":"REQUEST_LIMIT_EXCEEDED"}]', + metadata: proxyMetdata, + statusCode: 429, + }, + ], + statTags: statTags.throttled, + status: 429, + }, + }, + }, + }, + }, + { + id: 'salesforce_v1_scenario_5', + name: 'salesforce', + description: '[Proxy v1 API] :: Test for server unavailable scenario', + successCriteria: 'Should return 500 with error message "Server Unavailable"', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/5', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + 'Salesforce Request Failed - due to "Server Unavailable", (Retryable) during Salesforce Response Handling', + response: [ + { + error: '[{"message":"Server Unavailable","errorCode":"SERVER_UNAVAILABLE"}]', + metadata: proxyMetdata, + statusCode: 500, + }, + ], + statTags: statTags.retryable, + status: 500, + }, + }, + }, + }, + }, + { + id: 'salesforce_v1_scenario_6', + name: 'salesforce', + description: '[Proxy v1 API] :: Test for invalid grant scenario due to authentication failure', + successCriteria: + 'Should return 400 with error message "invalid_grant" due to "authentication failure"', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/6', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + 'Salesforce Request Failed: "400" due to "{"error":"invalid_grant","error_description":"authentication failure"}", (Aborted) during Salesforce Response Handling', + response: [ + { + error: '{"error":"invalid_grant","error_description":"authentication failure"}', + metadata: proxyMetdata, + statusCode: 400, + }, + ], + statTags: statTags.aborted, + status: 400, + }, + }, + }, + }, + }, + { + id: 'salesforce_v1_scenario_7', + name: 'salesforce', + description: '[Proxy v1 API] :: Test for a valid request - External ID search', + successCriteria: 'Should return 200 with list of matching records with External ID', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...externalIDSearchedData, + endpoint: + 'https://rudderstack.my.salesforce.com/services/data/v50.0/parameterizedSearch/?q=123&sobject=object_name&in=External_ID__c&object_name.fields=id,External_ID__c', + }, + reqMetadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request for destination: salesforce Processed Successfully', + response: [ + { + error: + '{"searchRecords":[{"attributes":{"type":"object_name","url":"/services/data/v50.0/sobjects/object_name/a0J75100002w97gEAA"},"Id":"a0J75100002w97gEAA","External_ID__c":"external_id"},{"attributes":{"type":"object_name","url":"/services/data/v50.0/sobjects/object_name/a0J75200002w9ZsEAI"},"Id":"a0J75200002w9ZsEAI","External_ID__c":"external_id TEST"}]}', + metadata: proxyMetdata, + statusCode: 200, + }, + ], + status: 200, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/salesforce/dataDelivery/data.ts b/test/integrations/destinations/salesforce/dataDelivery/data.ts index cfaa75e23e..d376289d97 100644 --- a/test/integrations/destinations/salesforce/dataDelivery/data.ts +++ b/test/integrations/destinations/salesforce/dataDelivery/data.ts @@ -1,7 +1,18 @@ import { AxiosError } from 'axios'; import MockAdapter from 'axios-mock-adapter'; +import { testScenariosForV1API } from './business'; +import { otherSalesforceScenariosV1 } from './other'; -export const data = [ +const legacyDataValue = { + Email: 'danis.archurav@sbermarket.ru', + Company: 'itus.ru', + LastName: 'Danis', + FirstName: 'Archurav', + LeadSource: 'App Signup', + account_type__c: 'free_trial', +}; + +const legacyTests = [ { name: 'salesforce', description: 'Test 0', @@ -24,14 +35,7 @@ export const data = [ body: { XML: {}, FORM: {}, - JSON: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + JSON: legacyDataValue, JSON_ARRAY: {}, }, metadata: { @@ -86,14 +90,7 @@ export const data = [ body: { XML: {}, FORM: {}, - JSON: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + JSON: legacyDataValue, JSON_ARRAY: {}, }, metadata: { @@ -162,14 +159,7 @@ export const data = [ body: { XML: {}, FORM: {}, - JSON: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + JSON: legacyDataValue, JSON_ARRAY: {}, }, metadata: { @@ -238,14 +228,7 @@ export const data = [ body: { XML: {}, FORM: {}, - JSON: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + JSON: legacyDataValue, JSON_ARRAY: {}, }, metadata: { @@ -314,14 +297,7 @@ export const data = [ body: { XML: {}, FORM: {}, - JSON: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + JSON: legacyDataValue, JSON_ARRAY: {}, }, metadata: { @@ -390,14 +366,7 @@ export const data = [ body: { XML: {}, FORM: {}, - JSON: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + JSON: legacyDataValue, JSON_ARRAY: {}, }, metadata: { @@ -464,14 +433,7 @@ export const data = [ body: { XML: {}, FORM: {}, - JSON: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + JSON: legacyDataValue, JSON_ARRAY: {}, }, metadata: { @@ -781,3 +743,4 @@ export const data = [ }, }, ]; +export const data = [...legacyTests, ...testScenariosForV1API, ...otherSalesforceScenariosV1]; diff --git a/test/integrations/destinations/salesforce/dataDelivery/other.ts b/test/integrations/destinations/salesforce/dataDelivery/other.ts new file mode 100644 index 0000000000..b3361caba7 --- /dev/null +++ b/test/integrations/destinations/salesforce/dataDelivery/other.ts @@ -0,0 +1,106 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateProxyV1Payload } from '../../../testUtils'; + +const statTags = { + errorCategory: 'network', + errorType: 'retryable', + destType: 'SALESFORCE', + module: 'destination', + implementation: 'native', + feature: 'dataDelivery', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', +}; +const metadata = { + jobId: 1, + attemptNum: 1, + userId: 'default-userId', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + sourceId: 'default-sourceId', + secret: { + accessToken: 'default-accessToken', + }, + dontBatch: false, +}; + +export const otherSalesforceScenariosV1: ProxyV1TestData[] = [ + { + id: 'salesforce_v1_other_scenario_1', + name: 'salesforce', + 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://sf_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: 500, + metadata, + }, + ], + statTags, + message: + 'Salesforce Request Failed - 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."}}", (Retryable) during Salesforce Response Handling', + status: 500, + }, + }, + }, + }, + }, + { + id: 'salesforce_v1_other_scenario_2', + name: 'salesforce', + 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, + message: + 'Salesforce Request Failed - due to ""Internal Server Error"", (Retryable) during Salesforce Response Handling', + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/salesforce/network.ts b/test/integrations/destinations/salesforce/network.ts index 396fad9d69..93013cd8db 100644 --- a/test/integrations/destinations/salesforce/network.ts +++ b/test/integrations/destinations/salesforce/network.ts @@ -1,15 +1,22 @@ +const commonHeaders = { + Authorization: 'Bearer token', + '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 tfProxyMocksData = [ { httpReq: { url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/1', - data: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + data: dataValue, params: { destination: 'salesforce' }, headers: { 'Content-Type': 'application/json', @@ -26,14 +33,7 @@ const tfProxyMocksData = [ { httpReq: { url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/3', - data: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + data: dataValue, params: { destination: 'salesforce' }, headers: { 'Content-Type': 'application/json', @@ -50,19 +50,11 @@ const tfProxyMocksData = [ { httpReq: { url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/2', - data: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + data: dataValue, params: { destination: 'salesforce' }, headers: { 'Content-Type': 'application/json', - Authorization: 'Bearer Incorrect_token', - 'User-Agent': 'RudderLabs', + Authorization: 'Bearer token', }, method: 'POST', }, @@ -74,14 +66,7 @@ const tfProxyMocksData = [ { httpReq: { url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/4', - data: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + data: dataValue, params: { destination: 'salesforce' }, headers: { 'Content-Type': 'application/json', @@ -98,14 +83,7 @@ const tfProxyMocksData = [ { httpReq: { url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/5', - data: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + data: dataValue, params: { destination: 'salesforce' }, headers: { 'Content-Type': 'application/json', @@ -122,14 +100,7 @@ const tfProxyMocksData = [ { httpReq: { url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/6', - data: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + data: dataValue, params: { destination: 'salesforce' }, headers: { 'Content-Type': 'application/json', @@ -146,19 +117,11 @@ const tfProxyMocksData = [ { httpReq: { url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/7', - data: { - Email: 'denis.kornilov@sbermarket.ru', - Company: 'sbermarket.ru', - LastName: 'Корнилов', - FirstName: 'Денис', - LeadSource: 'App Signup', - account_type__c: 'free_trial', - }, + data: dataValue, params: { destination: 'salesforce' }, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer token', - 'User-Agent': 'RudderLabs', }, method: 'POST', }, @@ -323,4 +286,185 @@ const transformationMocksData = [ }, }, ]; -export const networkCallsData = [...tfProxyMocksData, ...transformationMocksData]; + +const businessMockData = [ + { + 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: commonHeaders, + }, + httpRes: { + data: { statusText: 'No Content' }, + status: 204, + }, + }, + { + description: 'Mock response from destination depicting a invalid session id', + httpReq: { + method: 'post', + url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/invalid_session_id', + data: dataValue, + headers: commonHeaders, + }, + httpRes: { + data: [{ message: 'Session expired or invalid', errorCode: 'INVALID_SESSION_ID' }], + status: 500, + }, + }, + { + httpReq: { + url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/2', + data: dataValue, + params: { destination: 'salesforce' }, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer Incorrect_token', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: [{ message: 'INVALID_HEADER_TYPE', errorCode: 'INVALID_AUTH_HEADER' }], + status: 401, + }, + }, + { + httpReq: { + url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/4', + data: dataValue, + params: { destination: 'salesforce' }, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer token', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: [{ message: 'Request limit exceeded', errorCode: 'REQUEST_LIMIT_EXCEEDED' }], + status: 403, + }, + }, + { + httpReq: { + url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/5', + data: dataValue, + params: { destination: 'salesforce' }, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer token', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: [{ message: 'Server Unavailable', errorCode: 'SERVER_UNAVAILABLE' }], + status: 503, + }, + }, + { + httpReq: { + url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/6', + data: dataValue, + params: { destination: 'salesforce' }, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer token', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { error: 'invalid_grant', error_description: 'authentication failure' }, + status: 400, + }, + }, + { + httpReq: { + url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/sobjects/Lead/7', + data: dataValue, + params: { destination: 'salesforce' }, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer token', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { + message: 'Server Unavailable', + errorCode: 'SERVER_UNAVAILABLE', + }, + status: 503, + }, + }, + { + httpReq: { + url: 'https://rudderstack.my.salesforce.com/services/data/v50.0/parameterizedSearch/?q=123&sobject=object_name&in=External_ID__c&object_name.fields=id,External_ID__c', + data: { Planning_Categories__c: 'pc', External_ID__c: 123 }, + params: { destination: 'salesforce' }, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer token', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { + searchRecords: [ + { + attributes: { + type: 'object_name', + url: '/services/data/v50.0/sobjects/object_name/a0J75100002w97gEAA', + }, + Id: 'a0J75100002w97gEAA', + External_ID__c: 'external_id', + }, + { + attributes: { + type: 'object_name', + url: '/services/data/v50.0/sobjects/object_name/a0J75200002w9ZsEAI', + }, + Id: 'a0J75200002w9ZsEAI', + External_ID__c: 'external_id TEST', + }, + ], + }, + status: 200, + }, + }, +]; + +const otherMocksData = [ + { + description: + 'Mock response from destination depicting a valid lead request, with no changed data', + httpReq: { + method: 'post', + url: 'https://sf_test_url/test_for_service_not_available', + }, + httpRes: { + data: { + 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, + }, + }, +]; + +export const networkCallsData = [ + ...tfProxyMocksData, + ...transformationMocksData, + ...businessMockData, + ...otherMocksData +]; From a5d20ad7f6a71a176289f1a462e6853cfa67ec13 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Thu, 7 Mar 2024 18:30:20 +0530 Subject: [PATCH 29/37] chore: onboard script to generate testdata and test integration (#3112) --- .gitignore | 3 +- .../destinations/salesforce/network.ts | 2 +- test/integrations/testTypes.ts | 1 + test/integrations/testUtils.ts | 56 +++++++++--- test/scripts/testDataGenerator.ts | 88 +++++++++++++++++++ 5 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 test/scripts/testDataGenerator.ts diff --git a/.gitignore b/.gitignore index 956605f139..09c536ebb8 100644 --- a/.gitignore +++ b/.gitignore @@ -137,4 +137,5 @@ dist .idea # component test report -test_reports/ \ No newline at end of file +test_reports/ +temp/ diff --git a/test/integrations/destinations/salesforce/network.ts b/test/integrations/destinations/salesforce/network.ts index 93013cd8db..b422271d36 100644 --- a/test/integrations/destinations/salesforce/network.ts +++ b/test/integrations/destinations/salesforce/network.ts @@ -466,5 +466,5 @@ export const networkCallsData = [ ...tfProxyMocksData, ...transformationMocksData, ...businessMockData, - ...otherMocksData + ...otherMocksData, ]; diff --git a/test/integrations/testTypes.ts b/test/integrations/testTypes.ts index a46277d552..1c5a989f44 100644 --- a/test/integrations/testTypes.ts +++ b/test/integrations/testTypes.ts @@ -37,6 +37,7 @@ export interface mockType { } export interface TestCaseData { + id?: string; name: string; description: string; scenario?: string; diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts index 2abe4c6d9a..7aede97cf7 100644 --- a/test/integrations/testUtils.ts +++ b/test/integrations/testUtils.ts @@ -1,4 +1,3 @@ -import { z } from 'zod'; import { globSync } from 'glob'; import { join } from 'path'; import { MockHttpCallsData, TestCaseData } from './testTypes'; @@ -6,24 +5,18 @@ import MockAdapter from 'axios-mock-adapter'; import isMatch from 'lodash/isMatch'; import { OptionValues } from 'commander'; import { removeUndefinedAndNullValues } from '@rudderstack/integrations-lib'; -import { - Destination, - Metadata, - ProxyMetdata, - ProxyV0Request, - ProxyV1Request, -} from '../../src/types'; +import tags from '../../src/v0/util/tags'; +import { existsSync, mkdirSync, writeFileSync } from 'fs'; +import { Destination, ProxyMetdata, ProxyV0Request, ProxyV1Request } from '../../src/types'; import { DeliveryV0ResponseSchema, DeliveryV0ResponseSchemaForOauth, DeliveryV1ResponseSchema, DeliveryV1ResponseSchemaForOauth, ProcessorTransformationResponseListSchema, - ProcessorTransformationResponseSchema, ProxyV0RequestSchema, ProxyV1RequestSchema, RouterTransformationResponseListSchema, - RouterTransformationResponseSchema, } from '../../src/types/zodTypes'; const generateAlphanumericId = (size = 36) => @@ -104,6 +97,49 @@ export const overrideDestination = (destination: Destination, overrideConfigValu }); }; +export const produceTestData = (testData: TestCaseData[], filterKeys = []) => { + const result: any = []; + testData.forEach((tcData) => { + let events; + try { + switch (tcData.feature) { + case tags.FEATURES.PROCESSOR: + events = tcData.input.request.body; + break; + case tags.FEATURES.BATCH: + events = tcData.input.request.body.input; + break; + case tags.FEATURES.ROUTER: + events = tcData.input.request.body.input; + break; + } + } catch (e) { + throw new Error( + `Error in producing test data for destination:${tcData.name}, id:${tcData.id}: ${e}`, + ); + } + + events.forEach((event) => { + const { message } = event; + // remove unwanted keys + filterKeys.forEach((key) => { + delete message[key]; + }); + result.push(message); + }); + }); + + // write the data to a file + + // create directory if not exists + const dir = join(__dirname, '../../temp'); + if (!existsSync(dir)) { + mkdirSync(dir); + } + writeFileSync(join(__dirname, '../../temp/test_data.json'), JSON.stringify(result, null, 2)); + console.log('Data generated successfully at temp/test_data.json'); +}; + export const generateIndentifyPayload: any = (parametersOverride: any) => { const payload = { type: 'identify', diff --git a/test/scripts/testDataGenerator.ts b/test/scripts/testDataGenerator.ts new file mode 100644 index 0000000000..a00e9fce32 --- /dev/null +++ b/test/scripts/testDataGenerator.ts @@ -0,0 +1,88 @@ +import path from 'path'; +import { TestCaseData } from '../integrations/testTypes'; +import { getTestData, getTestDataFilePaths, produceTestData } from '../integrations/testUtils'; +import { Command } from 'commander'; +import axios from 'axios'; +import * as fs from 'fs'; + +// Produces test data for a given destination +// Example usage +// npx ts-node test/scripts/testDataGenerator.ts --destination=klaviyo --feature=processor + +const command = new Command(); +command + .allowUnknownOption() + .option('-d, --destination ', 'Enter Destination Name') + .option('-f, --feature ', 'Enter Feature Name(processor, router)') + .option('-i, --index ', 'Enter Test index') + .option('-id, --id ', 'Enter unique "Id" of the test case you want to run') + .option('-dp, --dataPlane ', 'Enter Data Plane URL') + .option('-wk, --writeKey ', 'Enter Write Key') + .option( + '-fk, --filterKeys ', + 'Enter Keys to filter from the test data(originalTimestamp, timestamp, messageId etc)', + ) + .parse(); + +const opts = command.opts(); + +if (opts.destination === undefined) { + throw new Error('Destination is not provided'); +} + +const filterKeys = opts.filterKeys ? opts.filterKeys.split(',') : []; + +const rootDir = __dirname; +const resolvedpath = path.resolve(rootDir, '../integrations'); +const destinationTestDataPaths = getTestDataFilePaths(resolvedpath, opts); + +destinationTestDataPaths.forEach((testDataPath) => { + let testData: TestCaseData[] = getTestData(testDataPath); + if (opts.index !== undefined) { + testData = [testData[parseInt(opts.index)]]; + } + if (opts.id) { + testData = testData.filter((data) => { + if (data['id'] === opts.id) { + return true; + } + return false; + }); + } + console.log('Writing test data to ../../temp/test_data.json'); + produceTestData(testData, filterKeys); + + if (opts.dataPlane && opts.writeKey) { + // read file ../../temp/test_data.json + console.log('Sending data to data plane URL: ', opts.dataPlane); + + const resolvedpathForData = path.resolve(rootDir, '../../temp/test_data.json'); + + fs.readFile(resolvedpathForData, 'utf8', function (err, data) { + if (err) { + console.log(err); + } else { + const parsedData = JSON.parse(data); + axios + .post( + `${opts.dataPlane}/v1/batch`, + { + batch: parsedData, + }, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Basic ${Buffer.from(opts.writeKey + ':').toString('base64')}`, + }, + }, + ) + .then((response) => { + console.log(response); + }) + .catch((error) => { + console.log(error); + }); + } + }); + } +}); From c1b3736ab60c9582bdf1c4b07a761976de0da16f Mon Sep 17 00:00:00 2001 From: Dilip Kola Date: Fri, 8 Mar 2024 11:46:43 +0530 Subject: [PATCH 30/37] fix: email mapping for clevertap --- .../clevertap/data/CleverTapIdentify.json | 2 +- .../destinations/clevertap/processor/data.ts | 124 ++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) 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/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', From 01d460c3edaf39b35c4686516c9e9140be46aa5e Mon Sep 17 00:00:00 2001 From: Sankeerth Date: Mon, 11 Mar 2024 12:48:09 +0530 Subject: [PATCH 31/37] fix: label not present in prometheus metrics (#3176) * fix: label not present in prometheus metrics Signed-off-by: Sai Sankeerth * fix: remove error logging Signed-off-by: Sai Sankeerth --- src/util/prometheus.js | 2 +- src/util/redis/redisConnector.test.js | 2 +- src/util/redis/testData/shopify_source.json | 15 ++++++++---- .../shopify/shopify_redis.util.test.js | 14 +++++++---- src/v0/sources/shopify/transform.js | 14 +++++++---- src/v0/sources/shopify/util.js | 23 +++++++++++++------ 6 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/util/prometheus.js b/src/util/prometheus.js index 0fa17dc9bd..89e5424c0c 100644 --- a/src/util/prometheus.js +++ b/src/util/prometheus.js @@ -710,7 +710,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/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, }); } }; From a0ca61bfd4fdb0197e40e39f9d21d49a97d726da Mon Sep 17 00:00:00 2001 From: ItsSudip Date: Mon, 11 Mar 2024 19:35:18 +0530 Subject: [PATCH 32/37] chore: address comments --- .../clevertap/dataDelivery/business.ts | 103 ++++++++++++++---- .../destinations/clevertap/network.ts | 29 +++++ 2 files changed, 113 insertions(+), 19 deletions(-) diff --git a/test/integrations/destinations/clevertap/dataDelivery/business.ts b/test/integrations/destinations/clevertap/dataDelivery/business.ts index edab4ee6d7..d9f83f52f3 100644 --- a/test/integrations/destinations/clevertap/dataDelivery/business.ts +++ b/test/integrations/destinations/clevertap/dataDelivery/business.ts @@ -10,6 +10,18 @@ const headers = { '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', @@ -133,16 +145,7 @@ export const V1BusinessTestScenarion: ProxyV1TestData[] = [ statusCode: 401, }, ], - statTags: { - destType: 'CLEVERTAP', - destinationId: 'default-destinationId', - errorCategory: 'network', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'default-workspaceId', - }, + statTags, }, }, }, @@ -201,16 +204,78 @@ export const V1BusinessTestScenarion: ProxyV1TestData[] = [ statusCode: 400, }, ], - statTags: { - destType: 'CLEVERTAP', - destinationId: 'default-destinationId', - errorCategory: 'network', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'default-workspaceId', + 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/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 = [ { From fe72a9db9aee53bcab66b21b2f77a344f7ed61e3 Mon Sep 17 00:00:00 2001 From: Abhimanyu Babbar Date: Mon, 11 Mar 2024 23:46:33 +0530 Subject: [PATCH 33/37] chore: added step to raise PR to dedicated enterprise customers in devops on merge to master (#2802) * chore: added step to raise PR to dedicated enterprise customers as well in devops on merge to master * chore: automatic devops pr raise issue fixed --------- Co-authored-by: anshulrudderstack Co-authored-by: anshulrudderstack <144046982+anshulrudderstack@users.noreply.github.com> Co-authored-by: Jayachand --- .../workflows/prepare-for-prod-dt-deploy.yml | 36 +++++++++++++++++ .../workflows/prepare-for-prod-rollback.yml | 40 ++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) 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 From 1ca039d64ebb1a18a0fc6b78ed5ee08528ad6b48 Mon Sep 17 00:00:00 2001 From: Gustavo Warmling Teixeira Date: Tue, 12 Mar 2024 01:21:19 -0300 Subject: [PATCH 34/37] feat: add Koala destination (#3122) * Add koala procWorkflow file Basic steps for koala destination * Add koala canonicalNames * Add Koala integration test processor data * Add User-Agent rudderstack header * Add identity and track steps Basic implementation of payload data per event_type * Add messageId to Track call * Include ip information * Update endpoint url profiles -> projects * Update src/cdk/v2/destinations/koala/procWorkflow.yaml Co-authored-by: Gauravudia <60897972+Gauravudia@users.noreply.github.com> * Update src/cdk/v2/destinations/koala/procWorkflow.yaml Co-authored-by: Gauravudia <60897972+Gauravudia@users.noreply.github.com> * Update src/cdk/v2/destinations/koala/procWorkflow.yaml Co-authored-by: Gauravudia <60897972+Gauravudia@users.noreply.github.com> * Add KOALA as a routerTransform * Fix wrong attr assignment * Add rtWorkflow file * Remove batch_mode step * Conside properties data when collecting ko_profile_id and email * Update src/cdk/v2/destinations/koala/procWorkflow.yaml Co-authored-by: Gauravudia <60897972+Gauravudia@users.noreply.github.com> * Update src/cdk/v2/destinations/koala/procWorkflow.yaml Co-authored-by: Gauravudia <60897972+Gauravudia@users.noreply.github.com> * Remove tool-versions, added by mistake * Use context variables * remove event attr from identity call * Update tests data * Update test/integrations/destinations/koala/processor/data.ts Co-authored-by: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> * Update test/integrations/destinations/koala/processor/data.ts Co-authored-by: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> * Update test/integrations/destinations/koala/processor/data.ts Co-authored-by: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> * Update src/cdk/v2/destinations/koala/procWorkflow.yaml Co-authored-by: Gauravudia <60897972+Gauravudia@users.noreply.github.com> * Do not remove email from properties lets keep email in properties --------- Co-authored-by: Gauravudia <60897972+Gauravudia@users.noreply.github.com> Co-authored-by: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> --- .../v2/destinations/koala/procWorkflow.yaml | 65 ++++ src/cdk/v2/destinations/koala/rtWorkflow.yaml | 31 ++ src/constants/destinationCanonicalNames.js | 1 + src/features.json | 3 +- .../destinations/koala/processor/data.ts | 319 ++++++++++++++++++ .../destinations/koala/router/data.ts | 200 +++++++++++ 6 files changed, 618 insertions(+), 1 deletion(-) create mode 100644 src/cdk/v2/destinations/koala/procWorkflow.yaml create mode 100644 src/cdk/v2/destinations/koala/rtWorkflow.yaml create mode 100644 test/integrations/destinations/koala/processor/data.ts create mode 100644 test/integrations/destinations/koala/router/data.ts 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/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/test/integrations/destinations/koala/processor/data.ts b/test/integrations/destinations/koala/processor/data.ts new file mode 100644 index 0000000000..9c1ea97a77 --- /dev/null +++ b/test/integrations/destinations/koala/processor/data.ts @@ -0,0 +1,319 @@ +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..fb0db3e3fb --- /dev/null +++ b/test/integrations/destinations/koala/router/data.ts @@ -0,0 +1,200 @@ +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, + }, + }, + }, + } + ] + } + } + } + } +] From 69afa97396b1933fc11ae962dc93c0fe30dd1f03 Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Tue, 12 Mar 2024 10:23:17 +0530 Subject: [PATCH 35/37] chore: add v1 proxy tests and mocks for Marketo (#3116) --- test/integrations/common/network.ts | 11 + .../marketo/dataDelivery/business.ts | 352 ++++++ .../destinations/marketo/dataDelivery/data.ts | 7 +- .../marketo/dataDelivery/other.ts | 266 ++++ .../destinations/marketo/network.ts | 1100 +++++++++++++++-- 5 files changed, 1663 insertions(+), 73 deletions(-) create mode 100644 test/integrations/destinations/marketo/dataDelivery/business.ts create mode 100644 test/integrations/destinations/marketo/dataDelivery/other.ts 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/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]; From 25b042cd9e47b6eb3cb5cc9f15d27f1a2d9605cc Mon Sep 17 00:00:00 2001 From: Yashasvi Bajpai <33063622+yashasvibajpai@users.noreply.github.com> Date: Tue, 12 Mar 2024 10:23:33 +0530 Subject: [PATCH 36/37] chore: add v1 proxy tests for MSL (#3130) --- .../dataDelivery/business.ts | 255 ++++++++++++++++++ .../marketo_static_list/dataDelivery/data.ts | 73 +---- .../marketo_static_list/dataDelivery/other.ts | 194 +++++++++++++ .../marketo_static_list/network.ts | 63 ----- 4 files changed, 458 insertions(+), 127 deletions(-) create mode 100644 test/integrations/destinations/marketo_static_list/dataDelivery/business.ts create mode 100644 test/integrations/destinations/marketo_static_list/dataDelivery/other.ts 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', From 1ef20d66f87c296b4efd3c6d7129cde87aca9af2 Mon Sep 17 00:00:00 2001 From: Sandeep Digumarty Date: Tue, 12 Mar 2024 10:33:07 +0530 Subject: [PATCH 37/37] chore: upgrade node version to v18.19.1 to resolve synk vulnerabilities (#3154) --- .nvmrc | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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