From 7d6ea123e08b793a87f35290e740cbef547c3862 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Thu, 25 Jan 2024 12:50:59 +0530 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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', + }, + }, + }, + }, + }, + }, +];