From 7d6ea123e08b793a87f35290e740cbef547c3862 Mon Sep 17 00:00:00 2001 From: Utsab Chowdhury Date: Thu, 25 Jan 2024 12:50:59 +0530 Subject: [PATCH 01/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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 c35a812f603d89c590837a1c0befb939dcbbd93d Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Thu, 8 Feb 2024 14:46:09 +0530 Subject: [PATCH 14/15] chore: ga4 proxy test refactor --- .../destinations/ga4/dataDelivery/business.ts | 276 ++++++++++++++++++ .../destinations/ga4/dataDelivery/data.ts | 3 + test/integrations/destinations/ga4/network.ts | 139 +++++++++ 3 files changed, 418 insertions(+) create mode 100644 test/integrations/destinations/ga4/dataDelivery/business.ts create mode 100644 test/integrations/destinations/ga4/dataDelivery/data.ts create mode 100644 test/integrations/destinations/ga4/network.ts diff --git a/test/integrations/destinations/ga4/dataDelivery/business.ts b/test/integrations/destinations/ga4/dataDelivery/business.ts new file mode 100644 index 0000000000..237da2fc3a --- /dev/null +++ b/test/integrations/destinations/ga4/dataDelivery/business.ts @@ -0,0 +1,276 @@ +import { ProxyMetdata } from '../../../../../src/types'; +import { generateProxyV1Payload } from '../../../testUtils'; +import { JSON_MIME_TYPE } from '../../../../../src/v0/util/constant'; + +const headers = { + HOST: 'www.google-analytics.com', + 'Content-Type': JSON_MIME_TYPE, +}; + +const params = { + api_secret: 'dymmyApiSecret', +}; + +const validRequest = { + events: [ + { + name: 'sign_up', + params: { + method: 'google', + engagement_time_msec: 1, + }, + }, + ], + user_id: 'dummyUserId', + client_id: 'dummyClientId', + non_personalized_ads: true, +}; + +const invalidEventNameRequest = { + events: [ + { + name: 'campaign@details', + params: { + term: 'summer+travel', + medium: 'cpc', + source: 'google', + content: 'logo link', + campaign: 'Summer_fun', + campaign_id: 'google_1234', + engagement_time_msec: 1, + }, + }, + ], + user_id: 'dummyUserId', + client_id: 'dummyClientId', + non_personalized_ads: true, +}; + +const invalidParameterValueRequest = { + events: [ + { + name: 'add_to_cart', + params: { + currency: 'USD', + value: 7.77, + engagement_time_msec: 1, + items: [ + { + item_id: '507f1f77bcf86cd799439011', + item_name: 'Monopoly: 3rd Edition', + coupon: 'SUMMER_FUN', + item_category: 'Apparel', + item_brand: 'Google', + item_variant: 'green', + price: '$19', + quantity: 2, + affiliation: 'Google Merchandise Store', + currency: 'USD', + item_list_id: 'related_products', + item_list_name: 'Related Products', + location_id: 'L_12345', + }, + ], + }, + }, + ], + user_id: 'dummyUserId', + client_id: 'dummyClientId', + non_personalized_ads: true, +}; + +const proxyMetdata: ProxyMetdata = { + jobId: 1, + attemptNum: 1, + userId: 'dummyUserId', + sourceId: 'dummySourceId', + destinationId: 'dummyDestinationId', + workspaceId: 'dummyWorkspaceId', + secret: {}, + dontBatch: false, +}; + +const metadataArray = [proxyMetdata]; + +export const testScenariosForV1API = [ + { + id: 'ga4_v1_scenario_1', + name: 'ga4', + 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( + { + headers, + params, + JSON: validRequest, + endpoint: 'https://www.google-analytics.com/debug/mp/collect', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: '[GA4 Response Handler] - Request Processed Successfully', + response: [ + { + error: '{"validationMessages":[]}', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + jobId: 1, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + }, + statusCode: 200, + }, + ], + status: 200, + }, + }, + }, + }, + }, + { + id: 'ga4_v1_scenario_2', + name: 'ga4', + description: + '[Proxy v1 API] :: Test for a invalid event name - where the destination responds with 200 with error for invalid event name', + successCriteria: 'Should return 200 with error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + JSON: invalidEventNameRequest, + endpoint: 'https://www.google-analytics.com/debug/mp/collect', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + 'Validation Server Response Handler:: Validation Error for ga4 of field path :events | NAME_INVALID-Event at index: [0] has invalid name [campaign@details]. Only alphanumeric characters and underscores are allowed.', + response: [ + { + error: + 'Validation Server Response Handler:: Validation Error for ga4 of field path :events | NAME_INVALID-Event at index: [0] has invalid name [campaign@details]. Only alphanumeric characters and underscores are allowed.', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + jobId: 1, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + }, + statusCode: 200, + }, + ], + statTags: { + destType: 'GA4', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, + status: 200, + }, + }, + }, + }, + }, + { + id: 'ga4_v1_scenario_3', + name: 'ga4', + description: + '[Proxy v1 API] :: Test for a invalid parameter value - where the destination responds with 200 with error for invalid parameter value', + successCriteria: 'Should return 200 with error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + headers, + params, + JSON: invalidParameterValueRequest, + endpoint: 'https://www.google-analytics.com/debug/mp/collect', + }, + metadataArray, + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: + 'Validation Server Response Handler:: Validation Error for ga4 of field path :undefined | INTERNAL_ERROR-Validation of item.price should prevent conversion from unsupported value [string_value: "$19"]', + response: [ + { + error: + 'Validation Server Response Handler:: Validation Error for ga4 of field path :undefined | INTERNAL_ERROR-Validation of item.price should prevent conversion from unsupported value [string_value: "$19"]', + metadata: { + attemptNum: 1, + destinationId: 'dummyDestinationId', + dontBatch: false, + jobId: 1, + secret: {}, + sourceId: 'dummySourceId', + userId: 'dummyUserId', + workspaceId: 'dummyWorkspaceId', + }, + statusCode: 200, + }, + ], + statTags: { + destType: 'GA4', + destinationId: 'dummyDestinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'dummyWorkspaceId', + }, + status: 200, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/ga4/dataDelivery/data.ts b/test/integrations/destinations/ga4/dataDelivery/data.ts new file mode 100644 index 0000000000..fc969bb8e1 --- /dev/null +++ b/test/integrations/destinations/ga4/dataDelivery/data.ts @@ -0,0 +1,3 @@ +import { testScenariosForV1API } from './business'; + +export const data = [...testScenariosForV1API]; diff --git a/test/integrations/destinations/ga4/network.ts b/test/integrations/destinations/ga4/network.ts new file mode 100644 index 0000000000..7451eb3b03 --- /dev/null +++ b/test/integrations/destinations/ga4/network.ts @@ -0,0 +1,139 @@ +const headers = { + HOST: 'www.google-analytics.com', + 'Content-Type': 'application/json', +}; + +const params = { + api_secret: 'dymmyApiSecret', +}; + +const businessMockData = [ + { + description: 'Mock response from destination depicting a valid request', + httpReq: { + method: 'post', + url: 'https://www.google-analytics.com/debug/mp/collect', + data: { + events: [ + { + name: 'sign_up', + params: { + method: 'google', + engagement_time_msec: 1, + }, + }, + ], + user_id: 'dummyUserId', + client_id: 'dummyClientId', + non_personalized_ads: true, + }, + headers, + params, + }, + httpRes: { + data: { + validationMessages: [], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response from destination depicting a invalid event name request', + httpReq: { + method: 'post', + url: 'https://www.google-analytics.com/debug/mp/collect', + data: { + events: [ + { + name: 'campaign@details', + params: { + term: 'summer+travel', + medium: 'cpc', + source: 'google', + content: 'logo link', + campaign: 'Summer_fun', + campaign_id: 'google_1234', + engagement_time_msec: 1, + }, + }, + ], + user_id: 'dummyUserId', + client_id: 'dummyClientId', + non_personalized_ads: true, + }, + headers, + params, + }, + httpRes: { + data: { + validationMessages: [ + { + fieldPath: 'events', + description: + 'Event at index: [0] has invalid name [campaign@details]. Only alphanumeric characters and underscores are allowed.', + validationCode: 'NAME_INVALID', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, + { + description: 'Mock response from destination depicting a invalid parameter value request', + httpReq: { + method: 'post', + url: 'https://www.google-analytics.com/debug/mp/collect', + data: { + events: [ + { + name: 'add_to_cart', + params: { + currency: 'USD', + value: 7.77, + engagement_time_msec: 1, + items: [ + { + item_id: '507f1f77bcf86cd799439011', + item_name: 'Monopoly: 3rd Edition', + coupon: 'SUMMER_FUN', + item_category: 'Apparel', + item_brand: 'Google', + item_variant: 'green', + price: '$19', + quantity: 2, + affiliation: 'Google Merchandise Store', + currency: 'USD', + item_list_id: 'related_products', + item_list_name: 'Related Products', + location_id: 'L_12345', + }, + ], + }, + }, + ], + user_id: 'dummyUserId', + client_id: 'dummyClientId', + non_personalized_ads: true, + }, + headers, + params, + }, + httpRes: { + data: { + validationMessages: [ + { + description: + 'Validation of item.price should prevent conversion from unsupported value [string_value: "$19"]', + validationCode: 'INTERNAL_ERROR', + }, + ], + }, + status: 200, + statusText: 'OK', + }, + }, +]; + +export const networkCallsData = [...businessMockData]; From d206a2407bcf557da4c3325df136770c56fdfd45 Mon Sep 17 00:00:00 2001 From: mihir-4116 Date: Tue, 27 Feb 2024 09:35:25 +0530 Subject: [PATCH 15/15] chore: pr conflict resolved --- .eslintrc.json | 4 +- .github/pull_request_template.md | 2 + .github/workflows/build-push-docker-image.yml | 2 +- .github/workflows/commitlint.yml | 36 + .github/workflows/component-test-report.yml | 25 +- .github/workflows/create-hotfix-branch.yml | 2 +- .github/workflows/draft-new-release.yml | 4 +- .../dt-test-and-report-code-coverage.yml | 8 +- .../workflows/prepare-for-staging-deploy.yml | 1 - .github/workflows/publish-new-release.yml | 4 +- .github/workflows/ut-tests.yml | 2 +- .github/workflows/verify.yml | 36 + .gitignore | 2 +- .prettierignore | 1 + .vscode/settings.json | 24 + CHANGELOG.md | 29 + README.md | 5 + package-lock.json | 394 +++---- package.json | 13 +- src/adapters/network.js | 6 + src/adapters/networkHandlerFactory.js | 5 +- .../bingads_audience/procWorkflow.yaml | 2 +- src/cdk/v2/destinations/bluecore/config.js | 56 + .../bluecore/data/bluecoreCommonConfig.json | 52 + .../bluecore/data/bluecoreIdentifyConfig.json | 7 + .../bluecore/data/bluecoreTrackConfig.json | 22 + .../destinations/bluecore/procWorkflow.yaml | 69 ++ src/cdk/v2/destinations/bluecore/utils.js | 250 +++++ .../v2/destinations/bluecore/utils.test.js | 403 +++++++ .../destinations/fullstory/procWorkflow.yaml | 13 +- .../v2/destinations/gladly/procWorkflow.yaml | 1 - .../v2/destinations/gladly/rtWorkflow.yaml | 2 +- .../v2/destinations/heap/procWorkflow.yaml | 2 - .../destinations/intercom/procWorkflow.yaml | 4 +- .../v2/destinations/intercom/rtWorkflow.yaml | 2 +- src/cdk/v2/destinations/intercom/utils.js | 38 +- .../v2/destinations/kochava/procWorkflow.yaml | 1 - .../v2/destinations/lytics/procWorkflow.yaml | 3 +- .../pinterest_tag/procWorkflow.yaml | 2 +- .../pinterest_tag/rtWorkflow.yaml | 2 +- .../rakuten/data/propertiesMapping.json | 76 +- src/cdk/v2/destinations/rakuten/utils.js | 2 +- .../v2/destinations/reddit/procWorkflow.yaml | 6 +- .../v2/destinations/sprig/procWorkflow.yaml | 2 - .../v2/destinations/statsig/procWorkflow.yaml | 1 - .../v2/destinations/the_trade_desk/config.js | 60 +- .../the_trade_desk/rtWorkflow.yaml | 18 +- .../the_trade_desk/transformConversion.js | 98 -- .../the_trade_desk/transformRecord.js | 18 +- .../v2/destinations/the_trade_desk/utils.js | 314 +----- .../destinations/the_trade_desk/utils.test.js | 624 +---------- .../config.js | 63 ++ .../data/TTDCommonConfig.json | 0 .../data/TTDItemConfig.json | 0 .../procWorkflow.yaml | 59 ++ .../utils.js | 315 ++++++ .../utils.test.js | 621 +++++++++++ .../tiktok_audience/procWorkflow.yaml | 2 - .../v2/destinations/zapier/procWorkflow.yaml | 1 - src/cdk/v2/handler.ts | 15 +- src/controllers/destination.ts | 15 + src/controllers/obs.delivery.js | 14 +- src/controllers/util/index.ts | 2 +- src/legacy/router.js | 3 +- src/services/destination/nativeIntegration.ts | 2 +- src/services/userTransform.ts | 2 +- src/types/index.ts | 1 + src/util/customTransformer-v1.js | 8 +- src/util/customTransformer.js | 6 +- src/util/customTransformerFactory.js | 24 +- src/util/error-extractor/index.ts | 34 +- src/util/error-extractor/types.ts | 2 +- src/util/ivmFactory.js | 10 +- src/util/prometheus.js | 12 +- src/util/stats.js | 8 +- .../destinations/active_campaign/transform.js | 12 + src/v0/destinations/adobe_analytics/utils.js | 2 +- src/v0/destinations/af/deleteUsers.js | 3 + src/v0/destinations/af/transform.js | 20 +- src/v0/destinations/am/config.js | 2 +- src/v0/destinations/am/deleteUsers.js | 3 + src/v0/destinations/am/transform.js | 20 +- src/v0/destinations/am/util.test.js | 35 +- src/v0/destinations/am/utils.js | 15 + src/v0/destinations/bqstream/transform.js | 5 - src/v0/destinations/braze/braze.util.test.js | 9 +- src/v0/destinations/braze/deleteUsers.js | 5 +- src/v0/destinations/braze/transform.js | 3 + src/v0/destinations/braze/util.js | 3 + .../campaign_manager/transform.js | 6 - src/v0/destinations/canny/util.js | 2 + src/v0/destinations/clevertap/deleteUsers.js | 3 + src/v0/destinations/clevertap/transform.js | 17 +- src/v0/destinations/clickup/util.js | 3 + src/v0/destinations/custify/deleteUsers.js | 58 +- src/v0/destinations/custify/util.js | 2 + src/v0/destinations/customerio/transform.js | 5 - src/v0/destinations/delighted/util.js | 8 +- src/v0/destinations/drip/util.js | 16 +- src/v0/destinations/engage/deleteUsers.js | 4 + src/v0/destinations/freshmarketer/utils.js | 14 + src/v0/destinations/freshsales/utils.js | 10 + src/v0/destinations/ga/deleteUsers.js | 2 + src/v0/destinations/ga4/networkHandler.js | 4 +- src/v0/destinations/gainsight/util.js | 24 +- src/v0/destinations/gainsight_px/util.js | 24 +- .../networkHandler.js | 4 + .../data/TrackAddStoreConversionsConfig.json | 12 +- .../networkHandler.js | 10 + .../transform.js | 9 +- .../utils.js | 2 + .../networkHandler.js | 6 + .../google_cloud_function/transform.js | 11 +- src/v0/destinations/googlesheets/transform.js | 5 - src/v0/destinations/hs/transform.js | 10 +- src/v0/destinations/hs/util.js | 10 + src/v0/destinations/intercom/deleteUsers.js | 3 + src/v0/destinations/iterable/deleteUsers.js | 4 + src/v0/destinations/iterable/transform.js | 6 - src/v0/destinations/kafka/transform.js | 5 - src/v0/destinations/klaviyo/transform.js | 5 - src/v0/destinations/klaviyo/util.js | 2 + src/v0/destinations/kustomer/util.js | 8 +- src/v0/destinations/lambda/transform.js | 3 +- src/v0/destinations/mailchimp/transform.js | 5 - src/v0/destinations/mailchimp/utils.js | 16 +- src/v0/destinations/mailjet/transform.js | 5 - src/v0/destinations/mailmodo/transform.js | 6 - src/v0/destinations/marketo/networkHandler.js | 2 +- src/v0/destinations/marketo/transform.js | 7 +- src/v0/destinations/marketo/util.js | 4 + .../marketo_bulk_upload/fetchJobStatus.js | 3 + .../marketo_bulk_upload/fileUpload.js | 3 + .../marketo_bulk_upload.util.test.js | 4 + .../destinations/marketo_bulk_upload/poll.js | 3 + .../destinations/marketo_bulk_upload/util.js | 6 + .../marketo_static_list/networkHandler.js | 2 +- .../marketo_static_list/transform.js | 12 +- .../marketo_static_list/transformV2.js | 7 +- src/v0/destinations/mautic/utils.js | 3 + src/v0/destinations/monday/util.js | 2 + src/v0/destinations/mp/deleteUsers.js | 5 + src/v0/destinations/mp/transform.js | 8 +- src/v0/destinations/mp/util.js | 91 -- src/v0/destinations/mp/util.test.js | 230 ---- src/v0/destinations/ometria/transform.js | 5 - src/v0/destinations/pardot/transform.js | 6 - .../destinations/pinterest_tag/transform.js | 6 - src/v0/destinations/profitwell/utils.js | 3 + src/v0/destinations/rakuten/networkHandler.js | 8 +- src/v0/destinations/salesforce/transform.js | 13 +- src/v0/destinations/salesforce/utils.js | 3 + src/v0/destinations/sendgrid/deleteUsers.js | 4 + src/v0/destinations/sendgrid/transform.js | 5 - src/v0/destinations/sendgrid/util.js | 3 + src/v0/destinations/sendinblue/util.js | 3 + src/v0/destinations/sfmc/transform.js | 8 +- .../snapchat_conversion/transform.js | 6 - .../networkHandler.js | 3 + src/v0/destinations/sprig/deleteUsers.js | 5 +- .../the_trade_desk/networkHandler.js | 48 +- src/v0/destinations/tiktok_ads/transform.js | 5 - src/v0/destinations/tiktok_ads/transformV2.js | 7 +- .../tiktok_ads_offline_events/config.js | 14 +- .../tiktok_ads_offline_events/transform.js | 6 - src/v0/destinations/trengo/transform.js | 8 +- src/v0/destinations/twitter_ads/config.js | 2 +- src/v0/destinations/twitter_ads/util.js | 29 +- src/v0/destinations/user/utils.js | 14 + src/v0/destinations/wootric/util.js | 4 + src/v0/destinations/yahoo_dsp/util.js | 3 + src/v0/destinations/zendesk/transform.js | 31 +- src/v0/sources/formsort/transform.js | 18 +- src/v0/sources/formsort/transform.test.js | 95 +- src/v0/sources/shopify/shopify.util.test.js | 4 +- src/v0/util/facebookUtils/index.js | 2 +- src/v0/util/index.js | 108 +- src/v0/util/index.test.js | 292 +++++- src/v0/util/tags.js | 3 +- src/warehouse/index.js | 2 +- src/warehouse/util.js | 2 +- .../data/sources/shopify/response.json | 2 +- .../data/customerio_source_input.json | 6 +- .../data/customerio_source_output.json | 2 +- test/__tests__/data/formsort_source.json | 178 ++-- test/__tests__/data/proxy_input.json | 2 +- test/__tests__/data/shopify.json | 90 +- .../destination/batch/failure_batch.json | 403 +++++-- test/integrations/common/criteo/network.ts | 72 ++ test/integrations/common/network.ts | 24 +- test/integrations/component.test.ts | 2 +- .../destinations/adj/processor/data.ts | 49 +- .../destinations/af/processor/data.ts | 305 ++++++ .../destinations/am/batch/data.ts | 132 +-- .../destinations/am/processor/data.ts | 53 + .../destinations/bluecore/data.ts | 6 + .../destinations/bluecore/ecommTestData.ts | 489 +++++++++ .../destinations/bluecore/identifyTestData.ts | 381 +++++++ .../destinations/bluecore/trackTestData.ts | 439 ++++++++ .../bluecore/validationTestData.ts | 150 +++ .../braze/dataDelivery/business.ts | 377 +++++++ .../destinations/braze/dataDelivery/data.ts | 6 +- .../destinations/braze/dataDelivery/other.ts | 204 ++++ .../destinations/braze/network.ts | 102 +- .../campaign_manager/dataDelivery/business.ts | 3 +- .../campaign_manager/dataDelivery/oauth.ts | 3 +- .../campaign_manager/dataDelivery/other.ts | 3 +- .../destinations/clevertap/network.ts | 3 +- .../destinations/clevertap/processor/data.ts | 16 +- .../destinations/clevertap/router/data.ts | 4 +- .../destinations/clickup/network.ts | 486 ++++----- .../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 ++-- .../destinations/custify/deleteUsers/data.ts | 158 +++ .../destinations/custify/network.ts | 115 +- .../destinations/delighted/network.ts | 54 +- .../fb_custom_audience/network.ts | 7 +- .../destinations/freshmarketer/network.ts | 956 ++++++++--------- .../destinations/freshsales/network.ts | 955 ++++++++--------- .../destinations/ga4/dataDelivery/business.ts | 8 +- .../destinations/gainsight/network.ts | 124 +-- .../destinations/gainsight_px/network.ts | 420 ++++---- .../router/data.ts | 554 ++++++---- .../router/data.ts | 294 +++--- .../destinations/iterable/deleteUsers/data.ts | 186 ++++ .../destinations/iterable/network.ts | 109 ++ .../destinations/klaviyo/network.ts | 128 ++- .../processor/transformationFailure.ts | 6 +- .../destinations/reddit/processor/data.ts | 167 +++ .../destinations/the_trade_desk/common.ts | 63 -- .../the_trade_desk/delivery/data.ts | 166 --- .../destinations/the_trade_desk/network.ts | 105 +- .../the_trade_desk/router/data.ts | 971 +---------------- .../common.ts | 91 ++ .../processor/data.ts | 984 ++++++++++++++++++ .../destinations/tiktok_ads/processor/data.ts | 103 ++ .../processor/data.ts | 244 +++++ .../destinations/wootric/network.ts | 347 +++--- test/integrations/testTypes.ts | 28 + test/integrations/testUtils.ts | 39 +- tsconfig.json | 6 +- 244 files changed, 11612 insertions(+), 6249 deletions(-) create mode 100644 .github/workflows/commitlint.yml create mode 100644 .github/workflows/verify.yml create mode 100644 .vscode/settings.json create mode 100644 src/cdk/v2/destinations/bluecore/config.js create mode 100644 src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json create mode 100644 src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json create mode 100644 src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json create mode 100644 src/cdk/v2/destinations/bluecore/procWorkflow.yaml create mode 100644 src/cdk/v2/destinations/bluecore/utils.js create mode 100644 src/cdk/v2/destinations/bluecore/utils.test.js delete mode 100644 src/cdk/v2/destinations/the_trade_desk/transformConversion.js create mode 100644 src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js rename src/cdk/v2/destinations/{the_trade_desk => the_trade_desk_real_time_conversions}/data/TTDCommonConfig.json (100%) rename src/cdk/v2/destinations/{the_trade_desk => the_trade_desk_real_time_conversions}/data/TTDItemConfig.json (100%) create mode 100644 src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml create mode 100644 src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js create mode 100644 src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js create mode 100644 test/integrations/common/criteo/network.ts create mode 100644 test/integrations/destinations/bluecore/data.ts create mode 100644 test/integrations/destinations/bluecore/ecommTestData.ts create mode 100644 test/integrations/destinations/bluecore/identifyTestData.ts create mode 100644 test/integrations/destinations/bluecore/trackTestData.ts create mode 100644 test/integrations/destinations/bluecore/validationTestData.ts create mode 100644 test/integrations/destinations/braze/dataDelivery/business.ts create mode 100644 test/integrations/destinations/braze/dataDelivery/other.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 create mode 100644 test/integrations/destinations/custify/deleteUsers/data.ts create mode 100644 test/integrations/destinations/iterable/deleteUsers/data.ts create mode 100644 test/integrations/destinations/iterable/network.ts create mode 100644 test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts create mode 100644 test/integrations/destinations/the_trade_desk_real_time_conversions/processor/data.ts diff --git a/.eslintrc.json b/.eslintrc.json index d2928e50fd..7258c5c536 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,9 +9,9 @@ "airbnb-base", "airbnb-typescript/base", "plugin:sonarjs/recommended", - "prettier", "plugin:json/recommended", - "plugin:@typescript-eslint/recommended" + "plugin:@typescript-eslint/recommended", + "prettier" ], "plugins": ["@typescript-eslint", "unicorn"], "globals": {}, diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index fe308ce8b9..cfcb1fc0d8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -26,6 +26,8 @@ N/A N/A +@coderabbitai review +
### Developer checklist diff --git a/.github/workflows/build-push-docker-image.yml b/.github/workflows/build-push-docker-image.yml index 9f6709a040..7ddae0a3ae 100644 --- a/.github/workflows/build-push-docker-image.yml +++ b/.github/workflows/build-push-docker-image.yml @@ -155,7 +155,7 @@ jobs: if: ${{ inputs.build_type == 'dt' }} run: | docker buildx imagetools create -t rudderstack/rudder-transformer:latest ${{ inputs.push_tags }}-amd64 ${{ inputs.push_tags }}-arm64 - + - name: Create latest ut multi-arch manifest # To be triggered only for release/hotfix PR merges coming from `prepare-for-prod-ut-deploy.yaml` if: ${{ inputs.build_type == 'ut' }} diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000000..a8ff39eee0 --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,36 @@ +name: Commitlint + +on: [push] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4.0.1 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Print versions + run: | + git --version + node --version + npm --version + npx commitlint --version + + # Run the commitlint action, considering its own dependencies and yours as well 🚀 + # `github.workspace` is the path to your repository. + - uses: wagoid/commitlint-github-action@v5 + env: + NODE_PATH: ${{ github.workspace }}/node_modules + with: + commitDepth: 1 diff --git a/.github/workflows/component-test-report.yml b/.github/workflows/component-test-report.yml index 9904004417..3d457df9ff 100644 --- a/.github/workflows/component-test-report.yml +++ b/.github/workflows/component-test-report.yml @@ -28,7 +28,7 @@ jobs: fetch-depth: 1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' @@ -40,13 +40,11 @@ jobs: run: | npm run test:ts -- component - - name: Uplaod Report to S3 run: | aws s3 cp ./test_reports/ s3://test-integrations-dev/integrations-test-reports/rudder-transformer/${{ github.event.number }}/ --recursive - - - name: Comment on PR with S3 Object URL + - name: Add Test Report Link as Comment on PR uses: actions/github-script@v7 with: github-token: ${{ secrets.PAT }} @@ -55,14 +53,25 @@ jobs: // Get the pull request number const prNumber = context.payload.pull_request.number; const commentBody = `Test report for this run is available at: https://test-integrations-dev.s3.amazonaws.com/integrations-test-reports/rudder-transformer/${prNumber}/test-report.html`; - + + // find all the comments of the PR + const issueComments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: prNumber, + }); + + for (const comment of issueComments) { + if (comment.body === commentBody) { + console.log('Comment already exists'); + return; + } + } // Comment on the pull request - github.rest.issues.createComment({ + await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: commentBody }); - - \ No newline at end of file diff --git a/.github/workflows/create-hotfix-branch.yml b/.github/workflows/create-hotfix-branch.yml index a164c25bee..994283a9f4 100644 --- a/.github/workflows/create-hotfix-branch.yml +++ b/.github/workflows/create-hotfix-branch.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest # Only allow these users to create new hotfix branch from 'main' - if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'koladilip' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'ujjwal-ab') + if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'koladilip' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'utsabc') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'utsabc') steps: - name: Create Branch uses: peterjgrainger/action-create-branch@v2.4.0 diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml index 33b0396705..95431fef8e 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest # Only allow release stakeholders to initiate releases - if: (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/')) && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'koladilip' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'yashasvibajpai' || github.actor == 'sanpj2292' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'yashasvibajpai' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'ujjwal-ab') + if: (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/')) && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'koladilip' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'yashasvibajpai' || github.actor == 'sanpj2292' || github.actor == 'utsabc') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'yashasvibajpai' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'utsabc') steps: - name: Checkout uses: actions/checkout@v4.1.1 @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/dt-test-and-report-code-coverage.yml b/.github/workflows/dt-test-and-report-code-coverage.yml index 4096227400..51a9f8c9ee 100644 --- a/.github/workflows/dt-test-and-report-code-coverage.yml +++ b/.github/workflows/dt-test-and-report-code-coverage.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' @@ -40,12 +40,14 @@ jobs: npm run lint:fix - name: Upload Coverage Reports to Codecov - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v4.0.1 with: directory: ./reports/coverage - name: Upload TS Coverage Reports to Codecov - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v4.0.1 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: directory: ./reports/ts-coverage diff --git a/.github/workflows/prepare-for-staging-deploy.yml b/.github/workflows/prepare-for-staging-deploy.yml index 4e8f29cffa..e7df8c43a5 100644 --- a/.github/workflows/prepare-for-staging-deploy.yml +++ b/.github/workflows/prepare-for-staging-deploy.yml @@ -68,7 +68,6 @@ jobs: secrets: DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} - create-pull-request: name: Update Helm Charts For Staging and Create Pull Request runs-on: ubuntu-latest diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml index 9d1558d826..233e99577d 100644 --- a/.github/workflows/publish-new-release.yml +++ b/.github/workflows/publish-new-release.yml @@ -30,7 +30,7 @@ jobs: fetch-depth: 0 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' @@ -89,7 +89,7 @@ jobs: - name: Notify Slack Channel id: slack - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@v1.25.0 continue-on-error: true env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/ut-tests.yml b/.github/workflows/ut-tests.yml index 30c29ceaee..0a2ef8a390 100644 --- a/.github/workflows/ut-tests.yml +++ b/.github/workflows/ut-tests.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 1 - name: Setup Node - uses: actions/setup-node@v4.0.1 + uses: actions/setup-node@v4.0.2 with: node-version-file: '.nvmrc' cache: 'npm' diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000000..4caef8dd91 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,36 @@ +name: Verify + +on: + pull_request: + +jobs: + formatting-lint: + name: Check for formatting & lint errors + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + # Make sure the actual branch is checked out when running on pull requests + ref: ${{ github.head_ref }} + + - name: Setup Node + uses: actions/setup-node@v3.7.0 + with: + node-version-file: .nvmrc + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Run Lint Checks + run: | + npm run lint + + - run: git diff --exit-code + + - name: Error message + if: ${{ failure() }} + run: | + echo 'Eslint check is failing Ensure you have run `npm run lint` and committed the files locally.' diff --git a/.gitignore b/.gitignore index f96c3ac807..956605f139 100644 --- a/.gitignore +++ b/.gitignore @@ -132,7 +132,7 @@ dist # Others **/.DS_Store - +.dccache .idea diff --git a/.prettierignore b/.prettierignore index 93eb370b0d..99747b29bb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,4 @@ test/**/*.js !test/**/data.js src/util/lodash-es-core.js src/util/url-search-params.min.js +dist diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..13c49c08f1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,24 @@ +{ + "prettier.requireConfig": true, + "prettier.configPath": ".prettierrc", + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "editor.codeActionsOnSave": { + "source.organizeImports": "never" + }, + "eslint.validate": ["javascript", "typescript"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 08032253ac..902d797301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [1.56.1](https://github.com/rudderlabs/rudder-transformer/compare/v1.56.0...v1.56.1) (2024-02-21) + + +### Bug Fixes + +* update proxy data type for response handler input ([#3030](https://github.com/rudderlabs/rudder-transformer/issues/3030)) ([457a18b](https://github.com/rudderlabs/rudder-transformer/commit/457a18b2aec03aa0dfafcadc611b5f7176e97beb)) + +## [1.56.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.55.0...v1.56.0) (2024-02-19) + + +### Features + +* **hs:** chunking data based on batch limit ([#2907](https://github.com/rudderlabs/rudder-transformer/issues/2907)) ([a60694c](https://github.com/rudderlabs/rudder-transformer/commit/a60694cef1da31d27a5cf90264548cad793f556f)) +* onboard bluecore integration ([#3061](https://github.com/rudderlabs/rudder-transformer/issues/3061)) ([aef5f8e](https://github.com/rudderlabs/rudder-transformer/commit/aef5f8e5f267262e0f9e10229f14f2bcc8ad29e2)) +* tiktok_offline_events added support for all Standard events ([#3094](https://github.com/rudderlabs/rudder-transformer/issues/3094)) ([b5cdccb](https://github.com/rudderlabs/rudder-transformer/commit/b5cdccb75fe68150816140174087fddad677db10)) + + +### Bug Fixes + +* add support of placing properties at root in af ([#3082](https://github.com/rudderlabs/rudder-transformer/issues/3082)) ([0f01524](https://github.com/rudderlabs/rudder-transformer/commit/0f01524b6f4f2f82efc21f88f8c97cb6fdaf91ea)) +* amplitude batch output metadata ([#3077](https://github.com/rudderlabs/rudder-transformer/issues/3077)) ([69c8348](https://github.com/rudderlabs/rudder-transformer/commit/69c83489c85486c9b2aed4a1292bd9f0aae9ca44)) +* amplitude: Error handling for missing event type ([#3079](https://github.com/rudderlabs/rudder-transformer/issues/3079)) ([f7ec0a1](https://github.com/rudderlabs/rudder-transformer/commit/f7ec0a1244a7b97e6b40de5ed9881c63300866dc)) +* custify user-regulation logic ([#3076](https://github.com/rudderlabs/rudder-transformer/issues/3076)) ([9683161](https://github.com/rudderlabs/rudder-transformer/commit/9683161612c7e3b9c2be95a2728f68ec7dcf69f4)) +* error handling for auth0 source ([#3038](https://github.com/rudderlabs/rudder-transformer/issues/3038)) ([2a21274](https://github.com/rudderlabs/rudder-transformer/commit/2a21274333350c615991f7b56b81b766502d5bf4)) +* **ga4:** failures not considered with 200 status in events tab ([#3089](https://github.com/rudderlabs/rudder-transformer/issues/3089)) ([6a364fb](https://github.com/rudderlabs/rudder-transformer/commit/6a364fba34c46b15c0fe4b06ecfa6f4b81b6f436)) +* gaoc batching order ([#3064](https://github.com/rudderlabs/rudder-transformer/issues/3064)) ([a98cabd](https://github.com/rudderlabs/rudder-transformer/commit/a98cabdfe7781ada12baf742df4a3a439fc5fecd)) +* resolve bugsnag issue caused due to undefined properties ([#3086](https://github.com/rudderlabs/rudder-transformer/issues/3086)) ([d522b35](https://github.com/rudderlabs/rudder-transformer/commit/d522b35c908a9f262ba3ba27dda0ea5d9ac5bc6b)) +* tiktok ads v2 error handling ([#3084](https://github.com/rudderlabs/rudder-transformer/issues/3084)) ([b6edff4](https://github.com/rudderlabs/rudder-transformer/commit/b6edff46fa0e0e210e82206fea46a064e3fbe00f)) + ## [1.55.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.54.4...v1.55.0) (2024-02-05) diff --git a/README.md b/README.md index 2b3908dcb5..4ff1cd4b34 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +

+⚠️ Docker image for rudder-transformer has been moved to new org rudderstack/rudder-transformer +

+

+ [![codecov](https://codecov.io/gh/rudderlabs/rudder-transformer/branch/develop/graph/badge.svg?token=G24OON85SB)](https://codecov.io/gh/rudderlabs/rudder-transformer) # RudderStack Transformer diff --git a/package-lock.json b/package-lock.json index b711cd7f72..0f44dfce3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rudder-transformer", - "version": "1.55.0", + "version": "1.56.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rudder-transformer", - "version": "1.55.0", + "version": "1.56.1", "license": "ISC", "dependencies": { "@amplitude/ua-parser-js": "0.7.24", @@ -19,8 +19,8 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/integrations-lib": "^0.2.2", - "@rudderstack/workflow-engine": "^0.6.9", + "@rudderstack/integrations-lib": "^0.2.4", + "@rudderstack/workflow-engine": "^0.7.2", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", @@ -91,9 +91,10 @@ "eslint": "^8.40.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-config-prettier": "^8.8.0", + "eslint-config-prettier": "^8.10.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-json": "^3.1.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-sonarjs": "^0.19.0", "eslint-plugin-unicorn": "^46.0.1", "glob": "^10.3.3", @@ -106,7 +107,7 @@ "madge": "^6.1.0", "mocked-env": "^1.3.5", "node-notifier": "^10.0.1", - "prettier": "^2.8.8", + "prettier": "^3.2.4", "semver": "^7.5.3", "standard-version": "^9.5.0", "supertest": "^6.3.3", @@ -154,7 +155,6 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3235,7 +3235,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -3250,7 +3249,6 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -3259,7 +3257,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -3282,7 +3279,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3297,14 +3293,12 @@ "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/@eslint/js": { "version": "8.56.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -3334,7 +3328,6 @@ "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", @@ -3348,7 +3341,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "engines": { "node": ">=12.22" }, @@ -3360,8 +3352,7 @@ "node_modules/@humanwhocodes/object-schema": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==" }, "node_modules/@hutson/parse-repository-url": { "version": "3.0.2", @@ -4348,7 +4339,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -4361,7 +4351,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -4370,7 +4359,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -4389,6 +4377,18 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -4468,14 +4468,15 @@ } }, "node_modules/@rudderstack/integrations-lib": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@rudderstack/integrations-lib/-/integrations-lib-0.2.2.tgz", - "integrity": "sha512-LilQsYcYh/4XXHNmYHM164fCbO5U3uvlw7k1wiCvFOR0MS1RhFXD9sPgCYpri683Jy3gqq1FrKN1EFj7oWAMjw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@rudderstack/integrations-lib/-/integrations-lib-0.2.4.tgz", + "integrity": "sha512-32Zose9aOPNWd4EyUNuS5YY+Vq4LYMuDcabJ+s3t1ZfHHMfISlDNF02b60MWgOrU8PARYC+siDs5wgA6xfZpzQ==", "dependencies": { - "@rudderstack/workflow-engine": "^0.5.7", "axios": "^1.4.0", "axios-mock-adapter": "^1.22.0", "crypto": "^1.0.1", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", "get-value": "^3.0.1", "handlebars": "^4.7.8", "lodash": "^4.17.21", @@ -4487,66 +4488,18 @@ "winston": "^3.11.0" } }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-4.0.0.tgz", - "integrity": "sha512-MHGJyjE7TX9aaqXj7zk2ppnFUOhaDs5sP+HtNS0evOxn72c+5njUmyJmpGd7TfyoDznZlHMmdo/xGUdu2NIjNQ==", - "dependencies": { - "@aws-crypto/util": "^4.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-4.0.0.tgz", - "integrity": "sha512-2EnmPy2gsFZ6m8bwUQN4jq+IyXV3quHAcwPOS6ZA3k+geujiqI8aRokO2kFJe+idJ/P3v4qWI186rVMo0+zLDQ==", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/json-template-engine": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.5.5.tgz", - "integrity": "sha512-p3HdTqgZiJjjZmjaHN2paa1e87ifGE5UjkA4zdvge4bBzJbKKMQNWqRg6I96SwoA+hsxNkW/f9R83SPLU9t7LA==" - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/workflow-engine": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.5.8.tgz", - "integrity": "sha512-H1aCowYqTnOoqJtL9cGDhdhoGNl+KzqmVbSjFmE7n75onZaIMs87+HQyW08jYxS9l1Uo4TL8SAvzFICqFqkBbw==", - "dependencies": { - "@aws-crypto/sha256-js": "^4.0.0", - "@rudderstack/json-template-engine": "^0.5.5", - "js-yaml": "^4.1.0", - "jsonata": "^2.0.3", - "lodash": "^4.17.21", - "object-sizeof": "^2.6.3" - } - }, "node_modules/@rudderstack/json-template-engine": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.2.tgz", - "integrity": "sha512-9oMBnqgNuwiXd7MUlNOAchCnJXQAy6w6XGmDqDM6iXdYDkvqYFiq7sbg5j4SdtpTTST293hahREr5PXfFVzVKg==" + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.5.tgz", + "integrity": "sha512-+iH40g+ZA2ANgwjOITdEdZJLZV+ljR28Akn/dRoDia591tMu7PptyvDaAvl+m1DijWXddpLQ8SX9xaEcIdmqlw==" }, "node_modules/@rudderstack/workflow-engine": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.6.10.tgz", - "integrity": "sha512-3GRdnbB0BuSPWiKf4JsSpG7QuGffAFWkT5T0JLR7Jxps25gt+PgtjQiAlwrRhO5A0WeTJMIKTI7ctz6dGmJosg==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.7.2.tgz", + "integrity": "sha512-aXQvoXMekvXxxDG6Yc5P5l3PJIwqVA+EmJ2w4SnQ94BUHhbsybPjgGvyzD17MUTAdWEOtqS38SuzLflBs/5T4g==", "dependencies": { "@aws-crypto/sha256-js": "^5.0.0", - "@rudderstack/json-template-engine": "^0.8.1", + "@rudderstack/json-template-engine": "^0.8.4", "jsonata": "^2.0.3", "lodash": "^4.17.21", "object-sizeof": "^2.6.3", @@ -5466,14 +5419,12 @@ "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/keygrip": { "version": "1.0.6", @@ -5555,8 +5506,7 @@ "node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==" }, "node_modules/@types/send": { "version": "0.17.4", @@ -5607,7 +5557,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -5641,7 +5590,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -5668,7 +5616,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0" @@ -5685,7 +5632,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, "dependencies": { "@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/utils": "5.62.0", @@ -5712,7 +5658,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5725,7 +5670,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0", @@ -5752,7 +5696,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", @@ -5778,7 +5721,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" @@ -5794,8 +5736,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/abbrev": { "version": "1.1.1", @@ -5818,7 +5759,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -5830,7 +5770,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -6030,7 +5969,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -6058,7 +5996,6 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6077,7 +6014,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, "engines": { "node": ">=8" } @@ -6086,7 +6022,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6105,7 +6040,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6123,7 +6057,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6141,7 +6074,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", @@ -6205,7 +6137,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -6620,7 +6551,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -6827,7 +6757,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -7559,8 +7488,7 @@ "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -8568,7 +8496,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -8741,8 +8668,7 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -8789,7 +8715,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -9162,7 +9087,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, "dependencies": { "path-type": "^4.0.0" }, @@ -9174,7 +9098,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -9404,7 +9327,6 @@ "version": "1.22.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "arraybuffer.prototype.slice": "^1.0.2", @@ -9457,7 +9379,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.2", "has-tostringtag": "^1.0.0", @@ -9471,7 +9392,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, "dependencies": { "hasown": "^2.0.0" } @@ -9480,7 +9400,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -9564,7 +9483,6 @@ "version": "8.56.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -9619,7 +9537,6 @@ "version": "15.0.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, "dependencies": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", @@ -9638,7 +9555,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -9647,7 +9563,6 @@ "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", - "dev": true, "dependencies": { "eslint-config-airbnb-base": "^15.0.0" }, @@ -9674,7 +9589,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -9685,7 +9599,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -9694,7 +9607,6 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, "dependencies": { "debug": "^3.2.7" }, @@ -9711,7 +9623,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -9720,7 +9631,6 @@ "version": "2.29.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", - "dev": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -9751,7 +9661,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -9760,7 +9669,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -9772,7 +9680,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -9790,6 +9697,36 @@ "node": ">=12.0" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-sonarjs": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.19.0.tgz", @@ -9941,7 +9878,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -9954,7 +9890,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -9966,7 +9901,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -9982,7 +9916,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -9997,7 +9930,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10013,7 +9945,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -10024,14 +9955,12 @@ "node_modules/eslint/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -10043,7 +9972,6 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -10059,7 +9987,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10068,7 +9995,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -10076,14 +10002,12 @@ "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -10095,7 +10019,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -10125,7 +10048,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -10137,7 +10059,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10146,7 +10067,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -10158,7 +10078,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10167,7 +10086,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10176,7 +10094,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10283,11 +10200,16 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -10303,7 +10225,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -10314,14 +10235,12 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/fast-printf": { "version": "1.6.9", @@ -10366,7 +10285,6 @@ "version": "1.16.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -10404,7 +10322,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -10467,7 +10384,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -10495,7 +10411,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -10542,7 +10457,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -10555,8 +10469,7 @@ "node_modules/flatted": { "version": "3.2.9", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, "node_modules/flatten": { "version": "1.0.3", @@ -10593,7 +10506,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -10745,7 +10657,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -10763,7 +10674,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11086,7 +10996,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -11342,7 +11251,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -11432,7 +11340,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -11447,7 +11354,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -11462,7 +11368,6 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -11559,8 +11464,7 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/growly": { "version": "1.3.0", @@ -11609,7 +11513,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11947,7 +11850,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", - "dev": true, "engines": { "node": ">= 4" } @@ -11956,7 +11858,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -11972,7 +11873,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -12000,7 +11900,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -12162,7 +12061,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.2", "hasown": "^2.0.0", @@ -12228,7 +12126,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -12247,7 +12144,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -12259,7 +12155,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -12312,7 +12207,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12335,7 +12229,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12365,7 +12258,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12421,7 +12313,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -12453,7 +12344,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12465,7 +12355,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -12474,7 +12363,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12507,7 +12395,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -12544,7 +12431,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -12584,7 +12470,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -12607,7 +12492,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12622,7 +12506,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -12649,7 +12532,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.11" }, @@ -12700,7 +12582,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -12743,8 +12624,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isobject": { "version": "3.0.1", @@ -14553,8 +14433,7 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/json-diff": { "version": "1.0.6", @@ -14599,8 +14478,7 @@ "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -14694,7 +14572,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "dependencies": { "json-buffer": "3.0.1" } @@ -14944,7 +14821,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -15294,7 +15170,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -15371,8 +15246,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/lodash.mergewith": { "version": "4.6.2", @@ -16149,7 +16023,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -16166,7 +16039,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -16653,14 +16525,12 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/natural-compare-lite": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" }, "node_modules/negotiator": { "version": "0.6.3", @@ -16883,7 +16753,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -16923,7 +16792,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "define-properties": "^1.2.1", @@ -16941,7 +16809,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16955,7 +16822,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16972,7 +16838,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16984,7 +16849,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -17055,7 +16919,6 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -17215,7 +17078,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -17277,7 +17139,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -17359,7 +17220,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -17403,7 +17263,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -17418,7 +17277,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -17913,7 +17771,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "engines": { "node": ">= 0.8.0" } @@ -17928,20 +17785,32 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -18136,7 +18005,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -18403,7 +18271,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -18604,7 +18471,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -18702,7 +18568,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -18734,7 +18599,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -18751,8 +18615,7 @@ "node_modules/safe-array-concat/node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/safe-buffer": { "version": "5.2.1", @@ -18786,7 +18649,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -18901,7 +18763,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "functions-have-names": "^1.2.3", @@ -18946,7 +18807,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -18958,7 +18818,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -19010,7 +18869,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, "engines": { "node": ">=8" } @@ -19696,7 +19554,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -19713,7 +19570,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -19727,7 +19583,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -19816,7 +19671,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -19930,6 +19784,22 @@ "node": ">=10" } }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -20027,8 +19897,7 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, "node_modules/through": { "version": "2.3.8", @@ -20093,7 +19962,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -20251,7 +20119,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -20263,7 +20130,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, "dependencies": { "minimist": "^1.2.0" }, @@ -20275,7 +20141,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, "engines": { "node": ">=4" } @@ -20297,7 +20162,6 @@ "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, "dependencies": { "tslib": "^1.8.1" }, @@ -20311,14 +20175,12 @@ "node_modules/tsutils/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -20339,7 +20201,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -20363,7 +20224,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -20377,7 +20237,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -20395,7 +20254,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -20414,7 +20272,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -20434,7 +20291,6 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20481,7 +20337,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -20749,7 +20604,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -20764,7 +20618,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -20786,7 +20639,6 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.4", diff --git a/package.json b/package.json index 8002398f5c..f6ab6bc1dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rudder-transformer", - "version": "1.55.0", + "version": "1.56.1", "description": "", "homepage": "https://github.com/rudderlabs/rudder-transformer#readme", "bugs": { @@ -19,9 +19,9 @@ "setup": "npm ci", "setup:swagger": "swagger-cli bundle swagger/api.yaml --outfile dist/swagger.json --type json", "format": "prettier --write .", - "lint": "eslint . || exit 0", "lint:fix": "eslint . --fix", "lint:fix:json": "eslint --ext .json --fix .", + "lint": "npm run format && npm run lint:fix", "check:merge": "npm run verify || exit 1; codecov", "start": "cd dist;node ./src/index.js;cd ..", "build:start": "npm run build && npm run start", @@ -64,8 +64,8 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/integrations-lib": "^0.2.2", - "@rudderstack/workflow-engine": "^0.6.9", + "@rudderstack/integrations-lib": "^0.2.4", + "@rudderstack/workflow-engine": "^0.7.2", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", @@ -136,9 +136,10 @@ "eslint": "^8.40.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-config-prettier": "^8.8.0", + "eslint-config-prettier": "^8.10.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-json": "^3.1.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-sonarjs": "^0.19.0", "eslint-plugin-unicorn": "^46.0.1", "glob": "^10.3.3", @@ -151,7 +152,7 @@ "madge": "^6.1.0", "mocked-env": "^1.3.5", "node-notifier": "^10.0.1", - "prettier": "^2.8.8", + "prettier": "^3.2.4", "semver": "^7.5.3", "standard-version": "^9.5.0", "supertest": "^6.3.3", diff --git a/src/adapters/network.js b/src/adapters/network.js index b0bd14374e..0720638d12 100644 --- a/src/adapters/network.js +++ b/src/adapters/network.js @@ -49,11 +49,15 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => { const destType = statTags.destType ? statTags.destType : ''; const feature = statTags.feature ? statTags.feature : ''; const endpointPath = statTags.endpointPath ? statTags.endpointPath : ''; + const requestMethod = statTags.requestMethod ? statTags.requestMethod : ''; + const module = statTags.module ? statTags.module : ''; const statusCode = clientResponse.success ? clientResponse.response.status : ''; stats.timing('outgoing_request_latency', startTime, { feature, destType, endpointPath, + requestMethod, + module, }); stats.counter('outgoing_request_count', 1, { feature, @@ -61,6 +65,8 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => { endpointPath, success: clientResponse.success, statusCode, + requestMethod, + module, }); }; diff --git a/src/adapters/networkHandlerFactory.js b/src/adapters/networkHandlerFactory.js index e8c3748d15..de80809a04 100644 --- a/src/adapters/networkHandlerFactory.js +++ b/src/adapters/networkHandlerFactory.js @@ -27,8 +27,9 @@ SUPPORTED_VERSIONS.forEach((version) => { // }, // generic: GenericNetworkHandler, // } - handlers[version][dest] = - require(`../${version}/destinations/${dest}/networkHandler`).networkHandler; + handlers[version][dest] = require( + `../${version}/destinations/${dest}/networkHandler`, + ).networkHandler; } catch { // Do nothing as exception indicates // network handler is not defined for that destination diff --git a/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml b/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml index 744c05a9a6..3292d66c69 100644 --- a/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml +++ b/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml @@ -50,4 +50,4 @@ steps: const response = $.defaultRequestConfig(); response.body.JSON = payload; response - ) \ No newline at end of file + ) diff --git a/src/cdk/v2/destinations/bluecore/config.js b/src/cdk/v2/destinations/bluecore/config.js new file mode 100644 index 0000000000..9b9cde9c66 --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/config.js @@ -0,0 +1,56 @@ +const { getMappingConfig } = require('../../../../v0/util'); + +const BASE_URL = 'https://api.bluecore.com/api/track/mobile/v1'; + +const CONFIG_CATEGORIES = { + IDENTIFY: { + name: 'bluecoreIdentifyConfig', + type: 'identify', + }, + TRACK: { + name: 'bluecoreTrackConfig', + type: 'track', + }, + COMMON: { + name: 'bluecoreCommonConfig', + type: 'common', + }, +}; + +const EVENT_NAME_MAPPING = [ + { + src: ['product viewed'], + dest: 'viewed_product', + }, + { + src: ['products searched'], + dest: 'search', + }, + { + src: ['product added'], + dest: 'add_to_cart', + }, + { + src: ['product removed'], + dest: 'remove_from_cart', + }, + { + src: ['product added to wishlist'], + dest: 'wishlist', + }, + { + src: ['order completed'], + dest: 'purchase', + }, +]; + +const BLUECORE_EXCLUSION_FIELDS = ['query', 'order_id', 'total']; + +const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); +module.exports = { + CONFIG_CATEGORIES, + MAPPING_CONFIG, + EVENT_NAME_MAPPING, + BASE_URL, + BLUECORE_EXCLUSION_FIELDS, +}; diff --git a/src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json b/src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json new file mode 100644 index 0000000000..be74c7c4b3 --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json @@ -0,0 +1,52 @@ +[ + { + "destKey": "properties.customer.name", + "sourceKeys": "name", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.customer.first_name", + "sourceKeys": "firstName", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.customer.last_name", + "sourceKeys": "lastName", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.customer.age", + "sourceKeys": ["context.traits.age", "traits.age"], + "required": false + }, + { + "destKey": "properties.customer.sex", + "sourceKeys": ["traits.gender", "context.traits.gender", "traits.sex", "context.traits.sex"], + "required": false + }, + { + "destKey": "properties.customer.address", + "sourceKeys": "address", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.customer.email", + "sourceKeys": "email", + "required": false, + "sourceFromGenericMap": true + }, + { + "destKey": "properties.client", + "sourceKeys": "context.app.version", + "required": false + }, + { + "destKey": "properties.device", + "sourceKeys": "context.device.model", + "required": false + } +] diff --git a/src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json b/src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json new file mode 100644 index 0000000000..5c3686f0ab --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json @@ -0,0 +1,7 @@ +[ + { + "destKey": "event", + "sourceKeys": ["traits.action", "context.traits.action"], + "required": false + } +] diff --git a/src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json b/src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json new file mode 100644 index 0000000000..8f6d59ec54 --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json @@ -0,0 +1,22 @@ +[ + { + "destKey": "properties.search_term", + "sourceKeys": "properties.query", + "required": false + }, + { + "destKey": "properties.order_id", + "sourceKeys": "properties.order_id", + "required": false + }, + { + "destKey": "properties.total", + "sourceKeys": "properties.total", + "required": false + }, + { + "destKey": "properties.products", + "sourceKeys": ["properties.products"], + "required": false + } +] diff --git a/src/cdk/v2/destinations/bluecore/procWorkflow.yaml b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml new file mode 100644 index 0000000000..480bced699 --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml @@ -0,0 +1,69 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + - name: defaultRequestConfig + path: ../../../../v0/util + - name: removeUndefinedNullValuesAndEmptyObjectArray + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - path: ./utils + - path: lodash + name: cloneDeep + +steps: + - name: messageType + template: | + .message.type.toLowerCase(); + - name: validateInput + template: | + let messageType = $.outputs.messageType; + $.assert(messageType, "message Type is not present. Aborting"); + $.assert(messageType in {{$.EventType.([.TRACK, .IDENTIFY])}}, "message type " + messageType + " is not supported"); + $.assertConfig(.destination.Config.bluecoreNamespace, "[BLUECORE] account namespace required for Authentication."); + - name: prepareIdentifyPayload + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} + template: | + const payload = $.constructProperties(.message); + payload.token = .destination.Config.bluecoreNamespace; + $.verifyPayload(payload, .message); + payload.event = payload.event ?? 'customer_patch'; + payload.properties.distinct_id = $.populateAccurateDistinctId(payload, .message); + $.context.payloads = [$.removeUndefinedAndNullValues(payload)]; + - name: handleTrackEvent + condition: $.outputs.messageType === {{$.EventType.TRACK}} + steps: + - name: validateInput + description: Additional validation for Track events + template: | + $.assert(.message.event, "event_name could not be mapped. Aborting.") + - name: deduceEventNames + template: | + $.context.deducedEventNameArray = $.deduceTrackEventName(.message.event,.destination.Config) + - name: preparePayload + template: | + const payload = $.constructProperties(.message); + $.context.payloads = $.context.deducedEventNameArray@eventName.( + const newPayload = $.cloneDeep(payload); + newPayload.properties.distinct_id = $.populateAccurateDistinctId(newPayload, ^.message); + const temporaryProductArray = newPayload.properties.products ?? $.createProductForStandardEcommEvent(^.message, eventName); + newPayload.properties.products = $.normalizeProductArray(temporaryProductArray); + newPayload.event = eventName; + newPayload.token = ^.destination.Config.bluecoreNamespace; + $.verifyPayload(newPayload, ^.message); + $.removeUndefinedNullValuesAndEmptyObjectArray(newPayload) + )[]; + + - name: buildResponse + template: | + $.context.payloads.( + const response = $.defaultRequestConfig(); + response.body.JSON = .; + response.method = "POST"; + response.endpoint = "https://api.bluecore.com/api/track/mobile/v1"; + response.headers = { + "Content-Type": "application/json" + }; + response + ) diff --git a/src/cdk/v2/destinations/bluecore/utils.js b/src/cdk/v2/destinations/bluecore/utils.js new file mode 100644 index 0000000000..22ec254fe2 --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/utils.js @@ -0,0 +1,250 @@ +const lodash = require('lodash'); + +const { + InstrumentationError, + isDefinedAndNotNullAndNotEmpty, + getHashFromArrayWithDuplicate, + isDefinedAndNotNull, + isDefinedNotNullNotEmpty, +} = require('@rudderstack/integrations-lib'); +const { + getFieldValueFromMessage, + validateEventName, + constructPayload, + getDestinationExternalID, +} = require('../../../../v0/util'); +const { CommonUtils } = require('../../../../util/common'); +const { EVENT_NAME_MAPPING } = require('./config'); +const { EventType } = require('../../../../constants'); +const { MAPPING_CONFIG, CONFIG_CATEGORIES } = require('./config'); + +/** + * Verifies the correctness of payload for different events. + * + * @param {Object} payload - The payload object containing event information. + * @param {Object} message - The message object containing additional information. + * @throws {InstrumentationError} - Throws an error if required properties are missing. + * @returns {void} + */ +const verifyPayload = (payload, message) => { + if ( + message.type === EventType.IDENTIFY && + isDefinedNotNullNotEmpty(message.traits?.action) && + message.traits?.action !== 'identify' + ) { + throw new InstrumentationError( + "[Bluecore] traits.action must be 'identify' for identify action", + ); + } + switch (payload.event) { + case 'search': + if (!payload?.properties?.search_term) { + throw new InstrumentationError( + '[Bluecore] property:: search_query is required for search event', + ); + } + break; + case 'purchase': + if (!payload?.properties?.order_id) { + throw new InstrumentationError( + '[Bluecore] property:: order_id is required for purchase event', + ); + } + if (!payload?.properties?.total) { + throw new InstrumentationError( + '[Bluecore] property:: total is required for purchase event', + ); + } + if ( + !isDefinedAndNotNull(payload?.properties?.customer) || + Object.keys(payload.properties.customer).length === 0 + ) { + throw new InstrumentationError( + `[Bluecore] property:: No relevant trait to populate customer information, which is required for ${payload.event} event`, + ); + } + break; + case 'identify': + case 'optin': + case 'unsubscribe': + if (!isDefinedAndNotNullAndNotEmpty(getFieldValueFromMessage(message, 'email'))) { + throw new InstrumentationError( + `[Bluecore] property:: email is required for ${payload.event} action`, + ); + } + if ( + !isDefinedAndNotNull(payload?.properties?.customer) || + Object.keys(payload.properties.customer).length === 0 + ) { + throw new InstrumentationError( + `[Bluecore] property:: No relevant trait to populate customer information, which is required for ${payload.event} action`, + ); + } + break; + default: + break; + } +}; + +/** + * Deduces the track event name based on the provided track event name and configuration. + * + * @param {string} trackEventName - The track event name to deduce. + * @param {object} Config - The configuration object. + * @returns {string|array} - The deduced track event name. + */ +const deduceTrackEventName = (trackEventName, destConfig) => { + let eventName; + const { eventsMapping } = destConfig; + validateEventName(trackEventName); + /* + Step 1: Will look for the event name in the eventsMapping array if mapped to a standard bluecore event. + and return the corresponding event name if found. + */ + if (eventsMapping.length > 0) { + const keyMap = getHashFromArrayWithDuplicate(eventsMapping, 'from', 'to', false); + eventName = keyMap[trackEventName]; + } + if (isDefinedAndNotNullAndNotEmpty(eventName)) { + const finalEvent = typeof eventName === 'string' ? [eventName] : [...eventName]; + return finalEvent; + } + + /* + Step 2: To find if the particular event is amongst the list of standard + Rudderstack ecommerce events, used specifically for Bluecore API + mappings. + */ + + const eventMapInfo = EVENT_NAME_MAPPING.find((eventMap) => + eventMap.src.includes(trackEventName.toLowerCase()), + ); + if (isDefinedAndNotNull(eventMapInfo)) { + return [eventMapInfo.dest]; + } + + // Step 3: if nothing matches this is to be considered as a custom event + return [trackEventName]; +}; + +/** + * Determines if the given event name is a standard Bluecore event. + * + * @param {string} eventName - The name of the event to check. + * @returns {boolean} - True if the event is a standard Bluecore event, false otherwise. + */ +const isStandardBluecoreEvent = (eventName) => { + // Return false immediately if eventName is an empty string or falsy + if (!eventName) { + return false; + } + // Proceed with the original check if eventName is not empty + return !!EVENT_NAME_MAPPING.some((item) => item.dest.includes(eventName)); +}; + +/** + * Adds an array of products to a message. + * + * @param {object} message - The message object to add the products to. + * @param {array|object} products - The array or object of products to add. + * @param {string} eventName - The name of the event. + * @throws {InstrumentationError} - If the products array is not defined or null. + * @returns {array} - The updated product array. + */ +const normalizeProductArray = (products) => { + let finalProductArray = null; + if (isDefinedAndNotNull(products)) { + const productArray = CommonUtils.toArray(products); + const mappedProductArray = productArray.map( + ({ product_id, sku, id, query, order_id, total, ...rest }) => ({ + id: product_id || sku || id, + ...rest, + }), + ); + finalProductArray = mappedProductArray; + } + // if any custom event is not sent with product array, then it should be null + return finalProductArray; +}; + +/** + * Constructs properties based on the given message. + * + * @param {object} message - The message object. + * @returns {object} - The constructed properties object. + */ +const constructProperties = (message) => { + const commonCategory = CONFIG_CATEGORIES.COMMON; + const commonPayload = constructPayload(message, MAPPING_CONFIG[commonCategory.name]); + const category = CONFIG_CATEGORIES[message.type.toUpperCase()]; + const typeSpecificPayload = constructPayload(message, MAPPING_CONFIG[category.name]); + const finalPayload = lodash.merge(commonPayload, typeSpecificPayload); + return finalPayload; +}; + +/** + * Creates a product for a standard e-commerce event. + * + * @param {Object} properties - The properties of the product. + * @param {string} eventName - The name of the event. + * @returns {Array|null} - An array containing the properties if the event is a standard Bluecore event and not 'search', otherwise null. + */ +const createProductForStandardEcommEvent = (message, eventName) => { + const { event, properties } = message; + if (event.toLowerCase() === 'order completed' && eventName === 'purchase') { + throw new InstrumentationError('[Bluecore]:: products array is required for purchase event'); + } + if (eventName !== 'search' && isStandardBluecoreEvent(eventName)) { + return [properties]; + } + return null; +}; +/** + * Function: populateAccurateDistinctId + * + * Description: + * This function is used to populate the accurate distinct ID based on the given payload and message. + * + * Parameters: + * - payload (object): The payload object containing the event and other data. + * - message (object): The message object containing the user data. + * + * Returns: + * - distinctId (string): The accurate distinct ID based on the given payload and message. + * + * Throws: + * - InstrumentationError: If the distinct ID could not be set. + * + */ +const populateAccurateDistinctId = (payload, message) => { + const bluecoreExternalId = getDestinationExternalID(message, 'bluecoreExternalId'); + if (isDefinedAndNotNullAndNotEmpty(bluecoreExternalId)) { + return bluecoreExternalId; + } + let distinctId; + if (payload.event === 'identify') { + distinctId = getFieldValueFromMessage(message, 'userId'); + } else { + // email is always a more preferred distinct_id + distinctId = + getFieldValueFromMessage(message, 'email') || getFieldValueFromMessage(message, 'userId'); + } + + if (!isDefinedAndNotNullAndNotEmpty(distinctId)) { + // dev safe. AnonymouId should be always present + throw new InstrumentationError( + '[Bluecore] property:: distinct_id could not be set. Please provide either email or userId or anonymousId or externalId as distinct_id.', + ); + } + return distinctId; +}; + +module.exports = { + verifyPayload, + deduceTrackEventName, + normalizeProductArray, + isStandardBluecoreEvent, + constructProperties, + createProductForStandardEcommEvent, + populateAccurateDistinctId, +}; diff --git a/src/cdk/v2/destinations/bluecore/utils.test.js b/src/cdk/v2/destinations/bluecore/utils.test.js new file mode 100644 index 0000000000..829073bbcc --- /dev/null +++ b/src/cdk/v2/destinations/bluecore/utils.test.js @@ -0,0 +1,403 @@ +const { + normalizeProductArray, + verifyPayload, + isStandardBluecoreEvent, + deduceTrackEventName, + populateAccurateDistinctId, + createProductForStandardEcommEvent, +} = require('./utils'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); + +describe('normalizeProductArray', () => { + // Adds an array of products to a message when products array is defined and not null. + it('should add an array of products to a message when products array is defined and not null', () => { + const products = [ + { product_id: 1, name: 'Product 1' }, + { product_id: 2, name: 'Product 2' }, + ]; + const eventName = 'purchase'; + + const result = normalizeProductArray(products, eventName); + + expect(result).toEqual([ + { id: 1, name: 'Product 1' }, + { id: 2, name: 'Product 2' }, + ]); + }); + + // Adds a single product object to a message when a single product object is passed. + it('should add a single product object to a message when a single product object is passed', () => { + const product = { product_id: 1, name: 'Product 1' }; + const eventName = 'add_to_cart'; + + const result = normalizeProductArray(product, eventName); + expect(result).toEqual([{ id: 1, name: 'Product 1' }]); + }); + + it('should not throw an InstrumentationError for a custom event when products array is null', () => { + const message = {}; + const products = null; + const eventName = 'custom'; + + expect(() => { + normalizeProductArray(message, products, eventName); + }).toBeNull; + }); +}); + +describe('verifyPayload', () => { + // Verify payload for search event with search_term property. + it('should verify payload for search event with search_term property', () => { + const payload = { + event: 'search', + properties: { + search_term: 'example', + }, + }; + expect(() => verifyPayload(payload, {})).not.toThrow(); + }); + + // Verify payload for purchase event with order_id and total properties. + it('should verify payload for purchase event with order_id and total and customer properties', () => { + const payload = { + event: 'purchase', + properties: { + order_id: '123', + total: 100, + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + // Verify payload for identify event with email property. + it('should verify payload for identify event with email property', () => { + const payload = { + event: 'identify', + properties: { + customer: { + first_name: 'John', + }, + }, + }; + const message = { + traits: { + email: 'test@example.com', + }, + }; + expect(() => verifyPayload(payload, message)).not.toThrow(); + }); + + // Verify payload for search event without search_term property, should throw an InstrumentationError. + it('should throw an InstrumentationError when verifying payload for search event without search_term property', () => { + const payload = { + event: 'search', + properties: {}, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + // Verify payload for purchase event without order_id property, should throw an InstrumentationError. + it('should throw an InstrumentationError when verifying payload for purchase event without order_id property', () => { + const payload = { + event: 'purchase', + properties: { + total: 100, + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + // Verify payload for purchase event without total property, should throw an InstrumentationError. + it('should throw an InstrumentationError when verifying payload for purchase event without total property', () => { + const payload = { + event: 'purchase', + properties: { + order_id: '123', + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + // Verify payload for purchase event without total property, should throw an InstrumentationError. + it('should throw an InstrumentationError when verifying payload for identify event with action field other than identify', () => { + const payload = { + event: 'random', + properties: { + email: 'abc@gmail.com', + }, + }; + expect(() => + verifyPayload(payload, { type: 'identify', traits: { action: 'random' } }), + ).toThrow(InstrumentationError); + }); + + it('should throw an InstrumentationError when verifying payload for optin event without email property', () => { + const payload = { + event: 'optin', + properties: { + order_id: '123', + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); + + it('should throw an InstrumentationError when verifying payload for unsubscribe event without email property', () => { + const payload = { + event: 'unsubscribe', + properties: { + order_id: '123', + }, + }; + expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError); + }); +}); + +describe('isStandardBluecoreEvent', () => { + // Returns true if the given event name is in the list of standard Bluecore events. + it('should return true when the given event name is in the list of standard Bluecore events', () => { + const eventName = 'search'; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(true); + }); + + // Returns false if the given event name is not in the list of standard Bluecore events. + it('should return false when the given event name is not in the list of standard Bluecore events', () => { + const eventName = 'someEvent'; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); + + // Returns false if the given event name is null. + it('should return false when the given event name is null', () => { + const eventName = null; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); + + // Returns false if the given event name is undefined. + it('should return false when the given event name is undefined', () => { + const eventName = undefined; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); + + // Returns false if the given event name is not a string. + it('should return false when the given event name is not a string', () => { + const eventName = 123; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); + + // Returns false if the given event name is an empty string. + it('should return false when the given event name is an empty string', () => { + const eventName = ''; + const result = isStandardBluecoreEvent(eventName); + expect(result).toBe(false); + }); +}); + +describe('deduceTrackEventName', () => { + // The function returns the trackEventName if no eventsMapping is provided and the trackEventName is not a standard Rudderstack ecommerce event. + it('should return the trackEventName when no eventsMapping is provided and the trackEventName is not a standard Rudderstack ecommerce event', () => { + const trackEventName = 'customEvent'; + const Config = { + eventsMapping: [], + }; + const result = deduceTrackEventName(trackEventName, Config); + expect(result).toEqual([trackEventName]); + }); + + // The function returns the corresponding event name from eventsMapping if the trackEventName is mapped to a standard bluecore event. + it('should return the corresponding event name from eventsMapping if the trackEventName is mapped to a standard bluecore event', () => { + const trackEventName = 'customEvent'; + const Config = { + eventsMapping: [{ from: 'customEvent', to: 'search' }], + }; + const result = deduceTrackEventName(trackEventName, Config); + expect(result).toEqual(['search']); + }); + + // The function returns the corresponding event name from eventsMapping if the trackEventName is mapped to a standard bluecore event. + it('should return the corresponding event name array from eventsMapping if the trackEventName is mapped to more than one standard bluecore events', () => { + const trackEventName = 'customEvent'; + const Config = { + eventsMapping: [ + { from: 'customEvent', to: 'search' }, + { from: 'customEvent', to: 'purchase' }, + ], + }; + const result = deduceTrackEventName(trackEventName, Config); + expect(result).toEqual(['search', 'purchase']); + }); + + // The function returns the corresponding standard Rudderstack ecommerce event name if the trackEventName is a standard bluecore event. + it('should return the corresponding standard Rudderstack ecommerce event name if the trackEventName is a standard bluecore event', () => { + const trackEventName = 'Product Added to Wishlist'; + const Config = { + eventsMapping: [], + }; + const result = deduceTrackEventName(trackEventName, Config); + expect(result).toEqual(['wishlist']); + }); + + // The function throws an error if the trackEventName is not a string. + it('should throw an error if the trackEventName is not a string', () => { + const trackEventName = 123; + const Config = { + eventsMapping: [], + }; + expect(() => deduceTrackEventName(trackEventName, Config)).toThrow(); + }); + + // The function throws an error if the trackEventName is an empty string. + it('should throw an error if the trackEventName is an empty string', () => { + const trackEventName = ''; + const Config = { + eventsMapping: [], + }; + expect(() => deduceTrackEventName(trackEventName, Config)).toThrow(); + }); +}); + +describe('populateAccurateDistinctId', () => { + // Returns the distinctId based on the email field when it exists in the message object and the event is not an identify event. + it('should return the distinctId based on the email field when it exists in the message object and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { userId: '123', context: { traits: { email: 'test@example.com' } } }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('test@example.com'); + }); + + // Returns the distinctId based on the userId field when it exists in the message object and the event is an identify event. + it('should return the distinctId based on the userId field when it exists in the message object and the event is an identify event', () => { + const payload = { event: 'identify' }; + const message = { userId: '123', context: { traits: { email: 'test@example.com' } } }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('123'); + }); + + // Returns the distinctId based on the userId field when it exists in the message object and the email field does not exist and the event is not an identify event. + it('should return the distinctId based on the userId field when it exists in the message object and the email field does not exist and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { userId: '123' }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('123'); + }); + + // Returns the distinctId based on the email field when it exists in the message object and the userId field is empty and the event is not an identify event. + it('should throw instrumenatation error as the message is malformed where email is at the root level', () => { + const payload = { event: 'event' }; + const message = { email: 'test@example.com', userId: '' }; + const testFn = () => populateAccurateDistinctId(payload, message); + expect(testFn).toThrow(InstrumentationError); + }); + + // Returns the distinctId based on the userId field when it exists in the message object and the email field is empty and the event is not an identify event. + it('should return the distinctId based on the userId field when it exists in the message object and the email field is empty and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { email: '', userId: '123' }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('123'); + }); + + // Returns the distinctId based on the anonymousId field when it exists in the message object and the email and userId fields are empty and the event is not an identify event. + it('should return the distinctId based on the anonymousId field when it exists in the message object and the email and userId fields are empty and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { anonymousId: 'abc' }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('abc'); + }); + + it('should return the distinctId based on the externalId field when it exists in the context object and the event is not an identify event', () => { + const payload = { event: 'event' }; + const message = { + userId: '123', + context: { + traits: { email: 'test@example.com' }, + externalId: [{ type: 'bluecoreExternalId', id: '54321' }], + }, + }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('54321'); + }); + + it('should return the distinctId based on the externalId field when it exists in the context object and the event is an identify event', () => { + const payload = { event: 'identify' }; + const message = { + userId: '123', + context: { + traits: { email: 'test@example.com' }, + externalId: [{ type: 'bluecoreExternalId', id: '54321' }], + }, + }; + const distinctId = populateAccurateDistinctId(payload, message); + expect(distinctId).toBe('54321'); + }); +}); + +describe('createProductForStandardEcommEvent', () => { + // Returns an array containing the properties if the event is a standard Bluecore event and not 'search'. + it("should return an array containing the properties when the event is a standard Bluecore event and not 'search'", () => { + const message = { + event: 'some event', + properties: { name: 'product 1' }, + }; + const eventName = 'some event'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toEqual(null); + }); + + // Returns null if the event is 'search'. + it("should return null when the event is 'search'", () => { + const message = { + event: 'search', + properties: { name: 'product 1' }, + }; + const eventName = 'search'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toBeNull(); + }); + + // Throws an InstrumentationError if the event is 'order completed' and the eventName is 'purchase'. + it("should throw an InstrumentationError when the event is 'order completed' and the eventName is 'purchase'", () => { + const message = { + event: 'order completed', + properties: { name: 'product 1' }, + }; + const eventName = 'purchase'; + expect(() => { + createProductForStandardEcommEvent(message, eventName); + }).toThrow(InstrumentationError); + }); + + // Returns null if the eventName is not a standard Bluecore event. + it('should return null when the eventName is not a standard Bluecore event', () => { + const message = { + event: 'some event', + properties: { name: 'product 1', products: [{ product_id: 1, name: 'prod1' }] }, + }; + const eventName = 'non-standard'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toBeNull(); + }); + + // Returns null if the eventName is not provided. + it('should return null when the eventName is not provided', () => { + const message = { + event: 'some event', + properties: { name: 'product 1' }, + }; + const result = createProductForStandardEcommEvent(message); + expect(result).toBeNull(); + }); + + // Returns null if the properties are not provided. + it('should return null when the properties are not provided', () => { + const message = { + event: 'some event', + }; + const eventName = 'some event'; + const result = createProductForStandardEcommEvent(message, eventName); + expect(result).toBeNull(); + }); +}); diff --git a/src/cdk/v2/destinations/fullstory/procWorkflow.yaml b/src/cdk/v2/destinations/fullstory/procWorkflow.yaml index 50ac2a8163..1a54e8688c 100644 --- a/src/cdk/v2/destinations/fullstory/procWorkflow.yaml +++ b/src/cdk/v2/destinations/fullstory/procWorkflow.yaml @@ -5,7 +5,7 @@ bindings: exportAll: true - name: removeUndefinedAndNullValues path: ../../../../v0/util - + steps: - name: validateInput template: | @@ -28,7 +28,7 @@ steps: $.context.payload.uid = .message.userId; $.context.payload.email = .message.context.traits.email; $.context.payload.display_name = .message.context.traits.name; - + - name: trackPayload condition: $.context.messageType == "track" template: | @@ -42,9 +42,9 @@ steps: condition: $.context.messageType == "track" template: | $.assert(.message.event, "event is required for track call") - + - name: mapContextFieldsForTrack - condition: $.context.messageType == "track" + condition: $.context.messageType == "track" template: | $.context.payload.context.browser = { "url": .message.context.page.url, @@ -67,7 +67,7 @@ steps: "region": .message.properties.region, "country": .message.properties.country, }; - + - name: mapIdsForTrack condition: $.context.messageType == "track" template: | @@ -99,6 +99,3 @@ steps: "params": {}, "files": {} }) - - - diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index fe8697bc31..a53a0ca8f5 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -13,7 +13,6 @@ bindings: path: ../../../../adapters/network - name: processAxiosResponse path: ../../../../adapters/utils/networkUtils - steps: - name: checkIfProcessed diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml index 341e5552c8..fc5d474d60 100644 --- a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml @@ -30,4 +30,4 @@ steps: )[] - name: finalPayload template: | - [...$.outputs.successfulEvents, ...$.outputs.failedEvents] \ No newline at end of file + [...$.outputs.successfulEvents, ...$.outputs.failedEvents] diff --git a/src/cdk/v2/destinations/heap/procWorkflow.yaml b/src/cdk/v2/destinations/heap/procWorkflow.yaml index 0191b75d18..8326a61a79 100644 --- a/src/cdk/v2/destinations/heap/procWorkflow.yaml +++ b/src/cdk/v2/destinations/heap/procWorkflow.yaml @@ -58,5 +58,3 @@ steps: "Content-Type": "application/json" }; response - - \ No newline at end of file diff --git a/src/cdk/v2/destinations/intercom/procWorkflow.yaml b/src/cdk/v2/destinations/intercom/procWorkflow.yaml index 4b2ca1869e..0a8842d5e7 100644 --- a/src/cdk/v2/destinations/intercom/procWorkflow.yaml +++ b/src/cdk/v2/destinations/intercom/procWorkflow.yaml @@ -71,7 +71,7 @@ steps: payload; - name: identifyPayloadForLatestVersion - condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1" + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1" template: | const payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.identifyTransformationForLatestVersion; payload.name = $.getName(.message); @@ -187,7 +187,7 @@ steps: template: | $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "companies"; - name: prepareFinalPayload - template: + template: | $.context.requestMethod = 'POST'; $.removeUndefinedAndNullValues($.context.payload); diff --git a/src/cdk/v2/destinations/intercom/rtWorkflow.yaml b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml index 3ed1046959..edb7267b84 100644 --- a/src/cdk/v2/destinations/intercom/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml @@ -30,4 +30,4 @@ steps: )[] - name: finalPayload template: | - [...$.outputs.successfulEvents, ...$.outputs.failedEvents] \ No newline at end of file + [...$.outputs.successfulEvents, ...$.outputs.failedEvents] diff --git a/src/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js index 0f18029f19..ba3063c9f9 100644 --- a/src/cdk/v2/destinations/intercom/utils.js +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -246,11 +246,20 @@ const searchContact = async (message, destination) => { const headers = getHeaders(destination); const baseEndPoint = getBaseEndpoint(destination); const endpoint = `${baseEndPoint}/${SEARCH_CONTACT_ENDPOINT}`; - const response = await httpPOST(endpoint, data, { - headers, - destType: 'intercom', - feature: 'transformation', - }); + const response = await httpPOST( + endpoint, + data, + { + headers, + }, + { + destType: 'intercom', + feature: 'transformation', + endpointPath: '/contacts/search', + requestMethod: 'POST', + module: 'router', + }, + ); const processedUserResponse = processAxiosResponse(response); if (isHttpStatusSuccess(processedUserResponse.status)) { return processedUserResponse.response?.data.length > 0 @@ -280,11 +289,20 @@ const createOrUpdateCompany = async (payload, destination) => { const finalPayload = JSON.stringify(removeUndefinedAndNullValues(payload)); const baseEndPoint = getBaseEndpoint(destination); const endpoint = `${baseEndPoint}/${CREATE_OR_UPDATE_COMPANY_ENDPOINT}`; - const response = await httpPOST(endpoint, finalPayload, { - headers, - destType: 'intercom', - feature: 'transformation', - }); + const response = await httpPOST( + endpoint, + finalPayload, + { + headers, + }, + { + destType: 'intercom', + feature: 'transformation', + endpointPath: '/companies', + requestMethod: 'POST', + module: 'router', + }, + ); const processedResponse = processAxiosResponse(response); if (isHttpStatusSuccess(processedResponse.status)) { diff --git a/src/cdk/v2/destinations/kochava/procWorkflow.yaml b/src/cdk/v2/destinations/kochava/procWorkflow.yaml index 557b1a0b63..3e73ee1520 100644 --- a/src/cdk/v2/destinations/kochava/procWorkflow.yaml +++ b/src/cdk/v2/destinations/kochava/procWorkflow.yaml @@ -10,7 +10,6 @@ bindings: - path: ../../bindings/jsontemplate - path: ./config.js - steps: - name: validateInput template: | diff --git a/src/cdk/v2/destinations/lytics/procWorkflow.yaml b/src/cdk/v2/destinations/lytics/procWorkflow.yaml index 1d6177fe09..2622146221 100644 --- a/src/cdk/v2/destinations/lytics/procWorkflow.yaml +++ b/src/cdk/v2/destinations/lytics/procWorkflow.yaml @@ -45,7 +45,7 @@ steps: $.context.payload._e = .message.event; - name: pageOrScreenPayload condition: $.context.messageType === {{$.EventType.PAGE}} || - $.context.messageType === {{$.EventType.SCREEN}} + $.context.messageType === {{$.EventType.SCREEN}} template: | $.context.payload.event = .message.name - name: cleanPaylod @@ -66,4 +66,3 @@ steps: "Content-Type": "application/json" }; response - diff --git a/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml b/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml index 8a956e905c..1122a80404 100644 --- a/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml +++ b/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml @@ -246,4 +246,4 @@ steps: }, "params": $.outputs.checkSendTestEventConfig, "files": {} - })[] \ No newline at end of file + })[] diff --git a/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml b/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml index 227942dfea..215ead12b1 100644 --- a/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml @@ -12,7 +12,7 @@ steps: If sendTestEvent is enabled, we send test event to the destination ref: https://help.pinterest.com/en/business/article/track-conversions-with-the-conversions-api template: | - ^[0].destination.Config.sendAsTestEvent ? {"test": true} : {} + ^[0].destination.Config.sendAsTestEvent ? {"test": true} : {} - name: transform externalWorkflow: diff --git a/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json index e04765faed..db5d36fc4d 100644 --- a/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json +++ b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json @@ -1,25 +1,34 @@ [ { - "sourceKeys": "properties.orderId", + "sourceKeys": ["properties.order_id", "properties.orderId"], "required": true, "destKey": "ord" }, { - "sourceKeys": ["properties.tr", "properties.ranSiteID"], + "sourceKeys": ["properties.tr", "properties.ran_site_id", "properties.ranSiteID"], "required": true, "destKey": "tr" }, { - "sourceKeys": ["properties.land", "properties.landTime"], + "sourceKeys": ["properties.land", "properties.land_time", "properties.landTime"], "required": true, "destKey": "land" }, { - "sourceKeys": ["properties.date", "properties.orderCompletedTime"], + "sourceKeys": [ + "properties.date", + "properties.order_completed_time", + "properties.orderCompletedTime" + ], "destKey": "date" }, { - "sourceKeys": ["properties.altord", "properties.alterOrderId"], + "sourceKeys": [ + "properties.alt_ord", + "properties.altord", + "properties.alter_order_id", + "properties.alterOrderId" + ], "destKey": "altord" }, { @@ -27,15 +36,15 @@ "destKey": "cur" }, { - "sourceKeys": "properties.creditCardType", + "sourceKeys": ["properties.credit_card_type", "properties.creditCardType"], "destKey": "cc" }, { - "sourceKeys": "properties.commReason", + "sourceKeys": ["properties.comm_reason", "properties.commReason"], "destKey": "commreason" }, { - "sourceKeys": "properties.isComm", + "sourceKeys": ["properties.is_comm", "properties.isComm"], "destKey": "iscomm" }, { @@ -47,27 +56,48 @@ "destKey": "coupon" }, { - "sourceKeys": ["properties.custId", "properties.customerId", "properties.userId"], + "sourceKeys": [ + "properties.cust_id", + "properties.custId", + "properties.customer_id", + "properties.customerId", + "properties.userId" + ], "destKey": "custid" }, { - "sourceKeys": ["properties.custScore", "properties.customerScore"], + "sourceKeys": [ + "properties.cust_score", + "properties.custScore", + "properties.customer_score", + "properties.customerScore" + ], "destKey": "custscore" }, { - "sourceKeys": ["properties.custStatus", "properties.customerStatus"], + "sourceKeys": [ + "properties.cust_status", + "properties.custStatus", + "properties.customer_status", + "properties.customerStatus" + ], "destKey": "custstatus" }, { - "sourceKeys": ["properties.dId", "properties.advertisingId"], + "sourceKeys": ["properties.dId", "properties.advertising_id", "properties.advertisingId"], "destKey": "did" }, { - "sourceKeys": ["properties.disamt", "properties.discountAmout"], + "sourceKeys": ["properties.disamt", "properties.discount_amount", "properties.discountAmount"], "destKey": "disamt" }, { - "sourceKeys": ["properties.ordStatus", "properties.orderStatus"], + "sourceKeys": [ + "properties.ord_status", + "properties.ordStatus", + "properties.order_status", + "properties.orderStatus" + ], "destKey": "ordstatus" }, { @@ -75,7 +105,7 @@ "destKey": "segment" }, { - "sourceKeys": "properties.shipcountry", + "sourceKeys": ["properties.ship_country", "properties.shipcountry"], "destKey": "shipcountry" }, { @@ -83,15 +113,25 @@ "destKey": "shipped" }, { - "sourceKeys": ["properties.sitename", "properties.url", "context.page.url"], + "sourceKeys": [ + "properties.site_name", + "properties.sitename", + "properties.url", + "context.page.url" + ], "destKey": "sitename" }, { - "sourceKeys": "properties.storeId", + "sourceKeys": ["properties.store_id", "properties.storeId"], "destKey": "storeid" }, { - "sourceKeys": ["properties.storecat", "properties.storeCategory"], + "sourceKeys": [ + "properties.store_cat", + "properties.storecat", + "properties.store_category", + "properties.storeCategory" + ], "destKey": "storecat" }, { diff --git a/src/cdk/v2/destinations/rakuten/utils.js b/src/cdk/v2/destinations/rakuten/utils.js index fe37455a57..ef6b197db7 100644 --- a/src/cdk/v2/destinations/rakuten/utils.js +++ b/src/cdk/v2/destinations/rakuten/utils.js @@ -59,7 +59,7 @@ const constructLineItems = (properties) => { } if (product.price) { - return product.quantity * product.price * 100; + return product.quantity * product.price * 100; } return product.amount * 100; }); diff --git a/src/cdk/v2/destinations/reddit/procWorkflow.yaml b/src/cdk/v2/destinations/reddit/procWorkflow.yaml index e6c05ef86d..59725c1257 100644 --- a/src/cdk/v2/destinations/reddit/procWorkflow.yaml +++ b/src/cdk/v2/destinations/reddit/procWorkflow.yaml @@ -57,12 +57,14 @@ steps: - name: customFields condition: $.outputs.prepareTrackPayload.eventType.tracking_type === "Purchase" + reference: 'https://ads-api.reddit.com/docs/v2/#tag/Conversions/paths/~1api~1v2.0~1conversions~1events~1%7Baccount_id%7D/post' template: | + const revenue_in_cents = .message.properties.revenue ? Math.round(Number(.message.properties.revenue)*100) const customFields = .message.().({ "currency": .properties.currency, - "value_decimal": .properties.revenue !== undefined ? Number(.properties.revenue) : undefined, + "value_decimal": revenue_in_cents ? revenue_in_cents / 100, "item_count": (Array.isArray(.properties.products) && .properties.products.length) || (.properties.itemCount && Number(.properties.itemCount)), - "value": .properties.revenue !== undefined ? Number(.properties.revenue)*100 : undefined, + "value": revenue_in_cents, "conversion_id": .properties.conversionId || .messageId, }); $.removeUndefinedAndNullValues(customFields) diff --git a/src/cdk/v2/destinations/sprig/procWorkflow.yaml b/src/cdk/v2/destinations/sprig/procWorkflow.yaml index 18b46913fd..4dcebeffcd 100644 --- a/src/cdk/v2/destinations/sprig/procWorkflow.yaml +++ b/src/cdk/v2/destinations/sprig/procWorkflow.yaml @@ -69,5 +69,3 @@ steps: "authorization": "API-Key " + .destination.Config.apiKey }; response - - diff --git a/src/cdk/v2/destinations/statsig/procWorkflow.yaml b/src/cdk/v2/destinations/statsig/procWorkflow.yaml index b3c85e31dc..6d3328b87e 100644 --- a/src/cdk/v2/destinations/statsig/procWorkflow.yaml +++ b/src/cdk/v2/destinations/statsig/procWorkflow.yaml @@ -26,4 +26,3 @@ steps: "content-type": "application/json" }; response - diff --git a/src/cdk/v2/destinations/the_trade_desk/config.js b/src/cdk/v2/destinations/the_trade_desk/config.js index 828bab3714..300325223b 100644 --- a/src/cdk/v2/destinations/the_trade_desk/config.js +++ b/src/cdk/v2/destinations/the_trade_desk/config.js @@ -1,6 +1,4 @@ -const { getMappingConfig } = require('../../../../v0/util'); - -const SUPPORTED_EVENT_TYPE = ['record', 'track']; +const SUPPORTED_EVENT_TYPE = ['record']; const ACTION_TYPES = ['insert', 'delete']; const DATA_PROVIDER_ID = 'rudderstack'; @@ -15,66 +13,10 @@ const DATA_SERVERS_BASE_ENDPOINTS_MAP = { china: 'https://data-cn2.adsrvr.cn', }; -// ref:- https://partner.thetradedesk.com/v3/portal/data/doc/DataConversionEventsApi -const REAL_TIME_CONVERSION_ENDPOINT = 'https://insight.adsrvr.org/track/realtimeconversion'; - -const CONVERSION_SUPPORTED_ID_TYPES = [ - 'TDID', - 'IDFA', - 'AAID', - 'DAID', - 'NAID', - 'IDL', - 'EUID', - 'UID2', -]; - -const ECOMM_EVENT_MAP = { - 'product added': { - event: 'addtocart', - rootLevelPriceSupported: true, - }, - 'order completed': { - event: 'purchase', - itemsArray: true, - revenueFieldSupported: true, - }, - 'product viewed': { - event: 'viewitem', - rootLevelPriceSupported: true, - }, - 'checkout started': { - event: 'startcheckout', - itemsArray: true, - revenueFieldSupported: true, - }, - 'cart viewed': { - event: 'viewcart', - itemsArray: true, - }, - 'product added to wishlist': { - event: 'wishlistitem', - rootLevelPriceSupported: true, - }, -}; - -const CONFIG_CATEGORIES = { - COMMON_CONFIGS: { name: 'TTDCommonConfig' }, - ITEM_CONFIGS: { name: 'TTDItemConfig' }, -}; - -const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); - module.exports = { SUPPORTED_EVENT_TYPE, ACTION_TYPES, DATA_PROVIDER_ID, MAX_REQUEST_SIZE_IN_BYTES: 2500000, DATA_SERVERS_BASE_ENDPOINTS_MAP, - CONVERSION_SUPPORTED_ID_TYPES, - CONFIG_CATEGORIES, - COMMON_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.COMMON_CONFIGS.name], - ITEM_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.ITEM_CONFIGS.name], - ECOMM_EVENT_MAP, - REAL_TIME_CONVERSION_ENDPOINT, }; diff --git a/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml b/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml index ee05ecd967..5f0476dd62 100644 --- a/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml @@ -3,20 +3,19 @@ bindings: path: ../../../../constants - name: processRecordInputs path: ./transformRecord - - name: processConversionInputs - path: ./transformConversion - name: handleRtTfSingleEventError path: ../../../../v0/util/index - name: InstrumentationError path: '@rudderstack/integrations-lib' steps: - - name: validateCommonConfig - description: | - validate common config for first party data and realtime conversion flow + - name: validateConfig template: | const config = ^[0].destination.Config + $.assertConfig(config.audienceId, "Segment name/Audience ID is not present. Aborting") $.assertConfig(config.advertiserId, "Advertiser ID is not present. Aborting") + $.assertConfig(config.advertiserSecretKey, "Advertiser Secret Key is not present. Aborting") + config.ttlInDays ? $.assertConfig(config.ttlInDays >=0 && config.ttlInDays <= 180, "TTL is out of range. Allowed values are 0 to 180 days") - name: validateInput template: | @@ -26,22 +25,17 @@ steps: template: | $.processRecordInputs(^.{.message.type === $.EventType.RECORD}[], ^[0].destination) - - name: processConversionEvents - template: | - $.processConversionInputs(^.{.message.type === $.EventType.TRACK}[]) - - name: failOtherEvents template: | - const otherEvents = ^.{.message.type !== $.EventType.TRACK && .message.type !== $.EventType.RECORD}[] + const otherEvents = ^.{.message.type !== $.EventType.RECORD}[] let failedEvents = otherEvents.map( function(event) { const error = new $.InstrumentationError("Event type " + event.message.type + " is not supported"); $.handleRtTfSingleEventError(event, error, {}) } ) - failedEvents ?? [] - name: finalPayload template: | - [...$.outputs.processRecordEvents, ...$.outputs.processConversionEvents, ...$.outputs.failOtherEvents] + [...$.outputs.processRecordEvents, ...$.outputs.failOtherEvents] diff --git a/src/cdk/v2/destinations/the_trade_desk/transformConversion.js b/src/cdk/v2/destinations/the_trade_desk/transformConversion.js deleted file mode 100644 index b282c43151..0000000000 --- a/src/cdk/v2/destinations/the_trade_desk/transformConversion.js +++ /dev/null @@ -1,98 +0,0 @@ -const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib'); -const { - defaultRequestConfig, - simpleProcessRouterDest, - defaultPostRequestConfig, - removeUndefinedAndNullValues, -} = require('../../../../v0/util'); -const { EventType } = require('../../../../constants'); -const { REAL_TIME_CONVERSION_ENDPOINT } = require('./config'); -const { - prepareFromConfig, - prepareCommonPayload, - getRevenue, - prepareItemsPayload, - getAdvertisingId, - prepareCustomProperties, - populateEventName, - getDataProcessingOptions, - getPrivacySetting, - enrichTrackPayload, -} = require('./utils'); - -const responseBuilder = (payload) => { - const response = defaultRequestConfig(); - response.endpoint = REAL_TIME_CONVERSION_ENDPOINT; - response.method = defaultPostRequestConfig.requestMethod; - response.body.JSON = payload; - return response; -}; - -const validateInputAndConfig = (message, destination) => { - const { Config } = destination; - if (!Config.trackerId) { - throw new ConfigurationError('Tracking Tag ID is not present. Aborting'); - } - - if (!message.type) { - throw new InstrumentationError('Event type is required'); - } - - const messageType = message.type.toLowerCase(); - if (messageType !== EventType.TRACK) { - throw new InstrumentationError(`Event type "${messageType}" is not supported`); - } - - if (!message.event) { - throw new InstrumentationError('Event name is not present. Aborting.'); - } -}; - -const prepareTrackPayload = (message, destination) => { - const configPayload = prepareFromConfig(destination); - const commonPayload = prepareCommonPayload(message); - // prepare items array - const items = prepareItemsPayload(message); - const { id, type } = getAdvertisingId(message); - // get td1-td10 custom properties - const customProperties = prepareCustomProperties(message, destination); - const eventName = populateEventName(message, destination); - const value = getRevenue(message); - let payload = { - ...configPayload, - ...commonPayload, - event_name: eventName, - value, - items, - adid: id, - adid_type: type, - ...customProperties, - data_processing_option: getDataProcessingOptions(message), - privacy_settings: getPrivacySetting(message), - }; - - payload = enrichTrackPayload(message, payload); - return { data: [removeUndefinedAndNullValues(payload)] }; -}; - -const trackResponseBuilder = (message, destination) => { - const payload = prepareTrackPayload(message, destination); - return responseBuilder(payload); -}; - -const processEvent = (message, destination) => { - validateInputAndConfig(message, destination); - return trackResponseBuilder(message, destination); -}; - -const process = (event) => processEvent(event.message, event.destination); - -const processConversionInputs = async (inputs, reqMetadata) => { - if (!inputs || inputs.length === 0) { - return []; - } - const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); - return respList; -}; - -module.exports = { processConversionInputs }; diff --git a/src/cdk/v2/destinations/the_trade_desk/transformRecord.js b/src/cdk/v2/destinations/the_trade_desk/transformRecord.js index d571e11b7a..b452f8d7bc 100644 --- a/src/cdk/v2/destinations/the_trade_desk/transformRecord.js +++ b/src/cdk/v2/destinations/the_trade_desk/transformRecord.js @@ -1,4 +1,4 @@ -const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); const { BatchUtils } = require('@rudderstack/workflow-engine'); const { defaultPostRequestConfig, @@ -13,20 +13,6 @@ const tradeDeskConfig = require('./config'); const { DATA_PROVIDER_ID } = tradeDeskConfig; -const validateConfig = (config) => { - if (!config.advertiserSecretKey) { - throw new ConfigurationError('Advertiser Secret Key is not present. Aborting'); - } - - if (config.ttlInDays && !(config.ttlInDays >= 0 && config.ttlInDays <= 180)) { - throw new ConfigurationError('TTL is out of range. Allowed values are 0 to 180 days'); - } - - if (!config.audienceId) { - throw new ConfigurationError('Segment name/Audience ID is not present. Aborting'); - } -}; - const responseBuilder = (items, config) => { const { advertiserId, dataServer } = config; @@ -64,8 +50,6 @@ const processRecordInputs = (inputs, destination) => { return []; } - validateConfig(Config); - const invalidActionTypeError = new InstrumentationError( 'Invalid action type. You can only add or remove IDs from the audience/segment', ); diff --git a/src/cdk/v2/destinations/the_trade_desk/utils.js b/src/cdk/v2/destinations/the_trade_desk/utils.js index f51d8dc3ff..71b479438c 100644 --- a/src/cdk/v2/destinations/the_trade_desk/utils.js +++ b/src/cdk/v2/destinations/the_trade_desk/utils.js @@ -1,28 +1,10 @@ -const lodash = require('lodash'); -const get = require('get-value'); const CryptoJS = require('crypto-js'); -const { InstrumentationError, AbortedError } = require('@rudderstack/integrations-lib'); -const { - constructPayload, - getHashFromArray, - isDefinedAndNotNull, - isAppleFamily, - getIntegrationsObj, - extractCustomFields, - generateExclusionList, -} = require('../../../../v0/util'); -const { - DATA_SERVERS_BASE_ENDPOINTS_MAP, - CONVERSION_SUPPORTED_ID_TYPES, - COMMON_CONFIGS, - ITEM_CONFIGS, - ECOMM_EVENT_MAP, -} = require('./config'); +const { AbortedError } = require('@rudderstack/integrations-lib'); +const { DATA_SERVERS_BASE_ENDPOINTS_MAP } = require('./config'); const getTTLInMin = (ttl) => parseInt(ttl, 10) * 1440; const getBaseEndpoint = (dataServer) => DATA_SERVERS_BASE_ENDPOINTS_MAP[dataServer]; const getFirstPartyEndpoint = (dataServer) => `${getBaseEndpoint(dataServer)}/data/advertiser`; -const prepareCommonPayload = (message) => constructPayload(message, COMMON_CONFIGS); /** * Generates a signature header for a given request using a secret key. @@ -41,300 +23,8 @@ const getSignatureHeader = (request, secretKey) => { return base; }; -const prepareFromConfig = (destination) => ({ - tracker_id: destination.Config?.trackerId, - adv: destination.Config?.advertiserId, -}); - -/** - * Calculates the revenue based on the given message. - * - * @param {Object} message - The message object containing the event and properties. - * @returns {number} - The calculated revenue. - * @throws {InstrumentationError} - If the event is 'Order Completed' and revenue is not provided. - */ -const getRevenue = (message) => { - const { event, properties } = message; - let revenue = properties?.value; - const eventsMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; - if (eventsMapInfo?.rootLevelPriceSupported) { - const { price, quantity = 1 } = properties; - if (price && !Number.isNaN(parseFloat(price)) && !Number.isNaN(parseInt(quantity, 10))) { - revenue = parseFloat(price) * parseInt(quantity, 10); - } - } else if (eventsMapInfo?.revenueFieldSupported) { - revenue = properties?.revenue || revenue; - if (event.toLowerCase() === 'order completed' && !revenue) { - throw new InstrumentationError('value is required for `Order Completed` event'); - } - } - - return revenue; -}; - -/** - * Generates items from properties of a given message. - * - * @param {Object} message - The message object containing properties. - * @returns {Array} - An array of items generated from the properties. - */ -const prepareItemsFromProperties = (message) => { - const { properties } = message; - const items = []; - const item = constructPayload(properties, ITEM_CONFIGS); - items.push(item); - return items; -}; - -/** - * Generates items payload from products. - * - * @param {Object} message - The message object. - * @returns {Array} - The items payload. - */ -const prepareItemsFromProducts = (message) => { - const products = get(message, 'properties.products'); - const items = []; - products.forEach((product) => { - const item = constructPayload(product, ITEM_CONFIGS); - const itemExclusionList = generateExclusionList(ITEM_CONFIGS); - extractCustomFields(product, item, 'root', itemExclusionList); - items.push(item); - }); - return items; -}; - -/** - * Generates items payload from root properties or products. - * - * @param {Object} message - The message object containing event and properties. - * @returns {Array} - The array of items payload. - */ -const prepareItemsPayload = (message) => { - const { event } = message; - let items; - const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; - if (eventMapInfo?.itemsArray) { - // if event is one of the supported ecommerce events and products array is present - items = prepareItemsFromProducts(message); - } else if (eventMapInfo) { - // if event is one of the supported ecommerce events and products array is not present - items = prepareItemsFromProperties(message); - } - return items; -}; - -/** - * Retrieves the device advertising ID and type based on the provided message. - * - * @param {Object} message - The message object containing the context. - * @returns {Object} - An object containing the device advertising ID and type. - */ -const getDeviceAdvertisingId = (message) => { - const { context } = message; - const deviceId = context?.device?.advertisingId; - const osName = context?.os?.name?.toLowerCase(); - - let type; - switch (osName) { - case 'android': - type = 'AAID'; - break; - case 'windows': - type = 'NAID'; - break; - default: - type = isAppleFamily(osName) ? 'IDFA' : undefined; - break; - } - - return { deviceId, type }; -}; - -/** - * Retrieves the external ID object from the given message context. - * - * @param {Object} message - The message object containing the context. - * @returns {Object|undefined} - The external ID object, or undefined if not found. - */ -const getDestinationExternalIDObject = (message) => { - const { context } = message; - const externalIdArray = context?.externalId || []; - - let externalIdObj; - - if (Array.isArray(externalIdArray)) { - externalIdObj = externalIdArray.find( - (extIdObj) => - CONVERSION_SUPPORTED_ID_TYPES.includes(extIdObj?.type?.toUpperCase()) && extIdObj?.id, - ); - } - return externalIdObj; -}; - -/** - * Retrieves the advertising ID and type from the given message. - * - * @param {Object} message - The message object containing the context. - * @returns {Object} - An object containing the advertising ID and type. - * If the advertising ID and type are found in the device context, they are returned. - * If not, the external ID object is checked and if found, its ID and type are returned. - * If neither the device context nor the external ID object contain the required information, - * an object with null values for ID and type is returned. - */ -const getAdvertisingId = (message) => { - const { deviceId, type } = getDeviceAdvertisingId(message); - if (deviceId && type) { - return { id: deviceId, type }; - } - const externalIdObj = getDestinationExternalIDObject(message); - if (externalIdObj?.id && externalIdObj?.type) { - return { id: externalIdObj.id, type: externalIdObj.type.toUpperCase() }; - } - - return { id: null, type: null }; -}; - -/** - * Prepares custom properties (td1-td10) for a given message and destination. - * - * @param {object} message - The message object. - * @param {object} destination - The destination object. - * @returns {object} - The prepared payload object. - */ -const prepareCustomProperties = (message, destination) => { - const { customProperties } = destination.Config; - const payload = {}; - if (customProperties) { - customProperties.forEach((customProperty) => { - const { rudderProperty, tradeDeskProperty } = customProperty; - const value = get(message, rudderProperty); - if (value) { - payload[tradeDeskProperty] = value; - // unset the rudder property from the message, since it is already mapped to a trade desk property - lodash.unset(message, rudderProperty); - } - }); - } - return payload; -}; - -/** - * Retrieves the event name based on the provided message and destination. - * - * @param {object} message - The message object containing the event. - * @param {object} destination - The destination object containing the events mapping configuration. - * @returns {string} - The event name. - */ -const populateEventName = (message, destination) => { - let eventName; - const { event } = message; - const { eventsMapping } = destination.Config; - - // if event is mapped on dashboard, use the mapped event name - if (Array.isArray(eventsMapping) && eventsMapping.length > 0) { - const keyMap = getHashFromArray(eventsMapping, 'from', 'to'); - eventName = keyMap[event.toLowerCase()]; - } - - if (eventName) { - return eventName; - } - - // if event is one of the supported ecommerce events, use the mapped trade desk event name - const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; - if (isDefinedAndNotNull(eventMapInfo)) { - return eventMapInfo.event; - } - - // else return the event name as it is - return event; -}; - -/** - * Retrieves the data processing options based on the provided message. - * - * @param {string} message - The message to process. - * @throws {InstrumentationError} - Throws an error if the region is not supported, if no policies are provided, if multiple policies are provided, or if the policy is not supported. - * @returns {Object} - The data processing options, including the policies and region. - */ -const getDataProcessingOptions = (message) => { - const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK') || {}; - let { policies } = integrationObj; - const { region } = integrationObj; - let dataProcessingOptions; - - if (region && !region.toLowerCase().startsWith('us')) { - throw new InstrumentationError('Only US states are supported'); - } - - if (!policies || (Array.isArray(policies) && policies.length === 0)) { - policies = ['LDU']; - } - - if (policies.length > 1) { - throw new InstrumentationError('Only one policy is allowed'); - } - - if (policies[0] !== 'LDU') { - throw new InstrumentationError('Only LDU policy is supported'); - } - - if (policies && region) { - dataProcessingOptions = { policies, region }; - } - - return dataProcessingOptions; -}; - -const getPrivacySetting = (message) => { - const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK'); - return integrationObj?.privacy_settings; -}; - -/** - * Enriches the track payload with extra properties present in 'properties' other than the ones defined in TTDCommonConfig.json and TTDItemConfig.json - * - * @param {Object} message - The message object containing the event information. - * @param {Object} payload - The payload object to be enriched. - * @returns {Object} - The enriched payload object. - */ -const enrichTrackPayload = (message, payload) => { - let rawPayload = { ...payload }; - const eventsMapInfo = ECOMM_EVENT_MAP[message.event.toLowerCase()]; - // checking if event is an ecomm one and itemsArray/products support is not present. e.g Product Added event - if (eventsMapInfo && !eventsMapInfo.itemsArray) { - const itemExclusionList = generateExclusionList(ITEM_CONFIGS); - rawPayload = extractCustomFields(message, rawPayload, ['properties'], itemExclusionList); - } else if (eventsMapInfo) { - // for ecomm events with products array supports. e.g Order Completed event - rawPayload = extractCustomFields( - message, - rawPayload, - ['properties'], - ['products', 'revenue', 'value'], - ); - } else { - // for custom events - rawPayload = extractCustomFields(message, rawPayload, ['properties'], ['value']); - } - return rawPayload; -}; - module.exports = { getTTLInMin, getFirstPartyEndpoint, getSignatureHeader, - prepareFromConfig, - getRevenue, - prepareCommonPayload, - prepareItemsPayload, - getDeviceAdvertisingId, - getDestinationExternalIDObject, - getAdvertisingId, - prepareCustomProperties, - populateEventName, - getDataProcessingOptions, - getPrivacySetting, - enrichTrackPayload, }; diff --git a/src/cdk/v2/destinations/the_trade_desk/utils.test.js b/src/cdk/v2/destinations/the_trade_desk/utils.test.js index 029c3004ae..81fd7cf17d 100644 --- a/src/cdk/v2/destinations/the_trade_desk/utils.test.js +++ b/src/cdk/v2/destinations/the_trade_desk/utils.test.js @@ -1,16 +1,5 @@ -const { AbortedError, InstrumentationError } = require('@rudderstack/integrations-lib'); -const { - getSignatureHeader, - getRevenue, - getDeviceAdvertisingId, - getDestinationExternalIDObject, - getAdvertisingId, - prepareCustomProperties, - populateEventName, - getDataProcessingOptions, - getPrivacySetting, - enrichTrackPayload, -} = require('./utils'); +const { AbortedError } = require('@rudderstack/integrations-lib'); +const { getSignatureHeader } = require('./utils'); describe('getSignatureHeader', () => { it('should calculate the signature header for a valid request and secret key', () => { @@ -58,612 +47,3 @@ describe('getSignatureHeader', () => { }).toThrow(AbortedError); }); }); - -describe('getRevenue', () => { - it('should return revenue value from message properties for custom events', () => { - const message = { - event: 'customEvent', - properties: { - value: 100, - }, - }; - const result = getRevenue(message); - expect(result).toBe(100); - }); - - it('should calculate revenue based on price and quantity from message properties if ecomm event is supported for price calculation', () => { - const message = { - event: 'Product Added', - properties: { - price: 10, - quantity: 5, - }, - }; - const result = getRevenue(message); - expect(result).toBe(50); - }); - - it('should return revenue value from message properties if ecomm event is supported for revenue calculation', () => { - const message = { - event: 'Order Completed', - properties: { - revenue: 200, - }, - }; - const result = getRevenue(message); - expect(result).toBe(200); - }); - - it('should return default revenue value from properties.value for ecomm events', () => { - let message = { - event: 'Product Added', - properties: { - price: '', - value: 200, - }, - }; - let result = getRevenue(message); - expect(result).toBe(200); - - message = { - event: 'Order Completed', - properties: { - value: 200, - }, - }; - result = getRevenue(message); - expect(result).toBe(200); - }); - - it('should throw an Instrumentation error if revenue is missing for `Order Completed` event', () => { - const message = { - event: 'Order Completed', - properties: {}, - }; - expect(() => { - getRevenue(message); - }).toThrow(InstrumentationError); - }); -}); - -describe('getDeviceAdvertisingId', () => { - it('should return an object with deviceId and type properties when context.device.advertisingId and context.os.name are present', () => { - let message = { - context: { - device: { - advertisingId: '123456789', - }, - os: { - name: 'android', - }, - }, - }; - let result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: '123456789', type: 'AAID' }); - - message = { - context: { - device: { - advertisingId: '123456789', - }, - os: { - name: 'ios', - }, - }, - }; - result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: '123456789', type: 'IDFA' }); - - message = { - context: { - device: { - advertisingId: '123456789', - }, - os: { - name: 'windows', - }, - }, - }; - result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: '123456789', type: 'NAID' }); - }); - - it('should return an object with undefined type property when osName is not "android", "windows", or an Apple OS', () => { - const message = { - context: { - device: { - advertisingId: '123456789', - }, - os: { - name: 'linux', - }, - }, - }; - const result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: '123456789', type: undefined }); - }); - - it('should return an object with undefined deviceId and type properties when context is undefined', () => { - let message = {}; - let result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: undefined, type: undefined }); - - message = { - context: {}, - }; - result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: undefined, type: undefined }); - - message = { - context: { - device: {}, - }, - }; - result = getDeviceAdvertisingId(message); - expect(result).toEqual({ deviceId: undefined, type: undefined }); - }); -}); - -describe('getDestinationExternalIDObject', () => { - it('should return the external ID object when it exists in the message context', () => { - const message = { - context: { - externalId: [ - { id: '123', type: 'daid' }, - { id: '456', type: 'type123' }, - ], - }, - }; - const result = getDestinationExternalIDObject(message); - expect(result).toEqual({ id: '123', type: 'daid' }); - }); - - it('should return undefined when no external ID object exists in the message context', () => { - let message = { - context: { - externalId: [], - }, - }; - let result = getDestinationExternalIDObject(message); - expect(result).toBeUndefined(); - - message = { - context: {}, - }; - result = getDestinationExternalIDObject(message); - expect(result).toBeUndefined(); - }); - - it('should return the first matching external ID object in the array', () => { - const message = { - context: { - externalId: [ - { id: '', type: 'daid' }, - { id: '456', type: 'tdid' }, - { id: '789', type: 'UID2' }, - ], - }, - }; - const result = getDestinationExternalIDObject(message); - expect(result).toEqual({ id: '456', type: 'tdid' }); - }); -}); - -describe('getAdvertisingId', () => { - it('should return an object with the ID and type when the message contains a valid device advertising ID and OS type', () => { - const message = { - context: { - device: { - advertisingId: '1234567890', - }, - os: { - name: 'android', - }, - }, - }; - - const result = getAdvertisingId(message); - expect(result).toEqual({ id: '1234567890', type: 'AAID' }); - }); - - it('should return an object with the ID and type when the message contains a valid external ID object with a supported type', () => { - const message = { - context: { - externalId: [ - { - type: 'IDFA', - id: 'abcdefg', - }, - ], - }, - }; - - const result = getAdvertisingId(message); - expect(result).toEqual({ id: 'abcdefg', type: 'IDFA' }); - }); - - it('should return an object with undefined ID and type when the message contains a valid external ID object with an unsupported type', () => { - let message = { - context: { - externalId: [ - { - type: 'unsupported', - id: '1234567890', - }, - ], - }, - }; - - let result = getAdvertisingId(message); - expect(result).toEqual({ id: null, type: null }); - - message = { - context: { - device: { - advertisingId: '1234567890', - }, - }, - }; - - result = getAdvertisingId(message); - expect(result).toEqual({ id: null, type: null }); - }); - - it('should return an object with undefined ID and type when the message contains an external ID object with a supported type but no ID or missing externalId', () => { - let message = { - context: { - externalId: [ - { - type: 'IDFA', - }, - ], - }, - }; - let result = getAdvertisingId(message); - expect(result).toEqual({ id: null, type: null }); - - message = { - context: {}, - }; - result = getAdvertisingId(message); - expect(result).toEqual({ id: null, type: null }); - }); -}); - -describe('prepareCustomProperties', () => { - it('should return an empty object when customProperties is an empty array', () => { - const message = {}; - let destination = { Config: { customProperties: [] } }; - let result = prepareCustomProperties(message, destination); - expect(result).toEqual({}); - - destination = { Config: { customProperties: [{ rudderProperty: '', tradeDeskProperty: '' }] } }; - result = prepareCustomProperties(message, destination); - expect(result).toEqual({}); - - destination = { Config: { customProperties: undefined } }; - result = prepareCustomProperties(message, destination); - expect(result).toEqual({}); - }); - - it('should return an object with `tradeDeskProperty` as key and `rudderProperty` value as value when `rudderProperty` exists in message', () => { - const message = { - rudderProperty1: 'value1', - rudderProperty2: 'value2', - }; - const destination = { - Config: { - customProperties: [ - { - rudderProperty: 'rudderProperty1', - tradeDeskProperty: 'tradeDeskProperty1', - }, - { - rudderProperty: 'rudderProperty2', - tradeDeskProperty: 'tradeDeskProperty2', - }, - { - rudderProperty: 'rudderProperty3', - tradeDeskProperty: 'tradeDeskProperty3', - }, - ], - }, - }; - const result = prepareCustomProperties(message, destination); - expect(result).toEqual({ - tradeDeskProperty1: 'value1', - tradeDeskProperty2: 'value2', - }); - }); -}); - -describe('populateEventName', () => { - it('should return the eventName if it exists in the eventsMapping of destination.Config', () => { - const message = { event: 'someEvent' }; - const destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } }; - const result = populateEventName(message, destination); - expect(result).toBe('mappedEvent'); - }); - - it('should return the eventName if it exists in the ECOMM_EVENT_MAP', () => { - const message = { event: 'product added' }; - let destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } }; - let result = populateEventName(message, destination); - expect(result).toBe('addtocart'); - - destination = { Config: { eventsMapping: [] } }; - result = populateEventName(message, destination); - expect(result).toBe('addtocart'); - }); - - it('should return undefined if eventsMapping is an empty array', () => { - const message = { event: 'someEvent' }; - const destination = { Config: { eventsMapping: [] } }; - const result = populateEventName(message, destination); - expect(result).toBe('someEvent'); - }); -}); - -describe('getDataProcessingOptions', () => { - it('should return an object with policies and region when provided a integrationObj in message', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: ['LDU'], - region: 'US-CO', - }, - }, - }; - const expected = { - policies: ['LDU'], - region: 'US-CO', - }; - const result = getDataProcessingOptions(message); - expect(result).toEqual(expected); - }); - - it('should throw an InstrumentationError if the region is not a US state', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: ['LDU'], - region: 'EU-abc', - }, - }, - }; - expect(() => { - getDataProcessingOptions(message); - }).toThrow(InstrumentationError); - }); - - it('should throw an InstrumentationError if multiple policies are provided', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: ['LDU', 'Policy2'], - region: 'US-CO', - }, - }, - }; - - expect(() => { - getDataProcessingOptions(message); - }).toThrow(InstrumentationError); - }); - - it('should throw an InstrumentationError if a policy other than `LDU` is provided', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: ['Policy1'], - region: 'US-CO', - }, - }, - }; - - expect(() => { - getDataProcessingOptions(message); - }).toThrow(InstrumentationError); - }); - - it('should return an object with default policy `LDU` when policies are not provided', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - policies: [], - region: 'US-CO', - }, - }, - }; - - const expected = { - policies: ['LDU'], - region: 'US-CO', - }; - - expect(getDataProcessingOptions(message)).toEqual(expected); - }); - - it('should handle empty cases', () => { - let message = { - integrations: { - All: true, - THE_TRADE_DESK: {}, - }, - }; - - expect(getDataProcessingOptions(message)).toBeUndefined(); - - message = { - integrations: { - All: true, - }, - }; - - expect(getDataProcessingOptions(message)).toBeUndefined(); - - message = { - integrations: { - All: true, - THE_TRADE_DESK: { region: 'US-CO' }, - }, - }; - - expect(getDataProcessingOptions(message)).toEqual({ policies: ['LDU'], region: 'US-CO' }); - }); -}); - -describe('getPrivacySetting', () => { - it('should return the privacy settings object when it exists in the integration object', () => { - const message = { - integrations: { - All: true, - THE_TRADE_DESK: { - privacy_settings: [ - { - privacy_type: 'GDPR', - is_applicable: 1, - consent_string: 'ok', - }, - ], - }, - }, - }; - const expected = [ - { - privacy_type: 'GDPR', - is_applicable: 1, - consent_string: 'ok', - }, - ]; - const result = getPrivacySetting(message); - expect(result).toEqual(expected); - }); - - it('should return null when the privacy settings object does not exist in the integration object', () => { - let message = { integrations: {} }; - expect(getPrivacySetting(message)).toBeUndefined(); - - message = { integrations: { THE_TRADE_DESK: {} } }; - expect(getPrivacySetting(message)).toBeUndefined(); - - message = { integrations: { THE_TRADE_DESK: { privacy_settings: null } } }; - expect(getPrivacySetting(message)).toBeNull(); - }); -}); - -describe('enrichTrackPayload', () => { - it('should correctly enrich the payload with custom fields for ecomm events where product array is not supported', () => { - const message = { - event: 'Product Added', - properties: { - product_id: 'prd123', - sku: 'sku123', - brand: 'brand123', - property1: 'value1', - property2: 'value2', - }, - }; - const payload = { - items: [ - { - item_code: 'prd123', - }, - ], - property1: 'value1', - property2: 'value2', - }; - const expectedPayload = { - items: [ - { - item_code: 'prd123', - }, - ], - brand: 'brand123', - property1: 'value1', - property2: 'value2', - }; - - const result = enrichTrackPayload(message, payload); - expect(result).toEqual(expectedPayload); - }); - - it('should correctly enrich the payload with custom fields when the for ecomm events with products array support', () => { - const message = { - event: 'order completed', - properties: { - order_id: 'ord123', - total: 52.0, - subtotal: 45.0, - revenue: 50.0, - products: [{ product_id: 'prd123', sku: 'sku123', brand: 'brand123' }], - property1: 'value1', - property2: 'value2', - }, - }; - const payload = { - order_id: 'ord123', - value: 50.0, - items: [{ item_code: 'prd123', brand: 'brand123' }], - property1: 'value1', - property2: 'value2', - }; - const expectedPayload = { - order_id: 'ord123', - total: 52.0, - subtotal: 45.0, - value: 50.0, - items: [{ item_code: 'prd123', brand: 'brand123' }], - property1: 'value1', - property2: 'value2', - }; - - const result = enrichTrackPayload(message, payload); - - expect(result).toEqual(expectedPayload); - }); - - it('should return the enriched payload for custom event', () => { - const message = { - event: 'someEvent', - properties: { - order_id: 'ord123', - property1: 'value1', - property2: 'value2', - revenue: 10, - value: 11, - products: [ - { - product_id: 'prd123', - test: 'test', - }, - ], - }, - }; - const payload = { - order_id: 'ord123', - value: 11, - }; - const expectedPayload = { - order_id: 'ord123', - property1: 'value1', - property2: 'value2', - revenue: 10, - value: 11, - products: [ - { - product_id: 'prd123', - test: 'test', - }, - ], - }; - - const result = enrichTrackPayload(message, payload); - expect(result).toEqual(expectedPayload); - }); -}); diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js new file mode 100644 index 0000000000..7af732185f --- /dev/null +++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js @@ -0,0 +1,63 @@ +const { getMappingConfig } = require('../../../../v0/util'); + +const SUPPORTED_EVENT_TYPE = ['track']; + +// ref:- https://partner.thetradedesk.com/v3/portal/data/doc/DataConversionEventsApi +const REAL_TIME_CONVERSION_ENDPOINT = 'https://insight.adsrvr.org/track/realtimeconversion'; + +const CONVERSION_SUPPORTED_ID_TYPES = [ + 'TDID', + 'IDFA', + 'AAID', + 'DAID', + 'NAID', + 'IDL', + 'EUID', + 'UID2', +]; + +const ECOMM_EVENT_MAP = { + 'product added': { + event: 'addtocart', + rootLevelPriceSupported: true, + }, + 'order completed': { + event: 'purchase', + itemsArray: true, + revenueFieldSupported: true, + }, + 'product viewed': { + event: 'viewitem', + rootLevelPriceSupported: true, + }, + 'checkout started': { + event: 'startcheckout', + itemsArray: true, + revenueFieldSupported: true, + }, + 'cart viewed': { + event: 'viewcart', + itemsArray: true, + }, + 'product added to wishlist': { + event: 'wishlistitem', + rootLevelPriceSupported: true, + }, +}; + +const CONFIG_CATEGORIES = { + COMMON_CONFIGS: { name: 'TTDCommonConfig' }, + ITEM_CONFIGS: { name: 'TTDItemConfig' }, +}; + +const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); + +module.exports = { + SUPPORTED_EVENT_TYPE, + CONFIG_CATEGORIES, + CONVERSION_SUPPORTED_ID_TYPES, + COMMON_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.COMMON_CONFIGS.name], + ITEM_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.ITEM_CONFIGS.name], + ECOMM_EVENT_MAP, + REAL_TIME_CONVERSION_ENDPOINT, +}; diff --git a/src/cdk/v2/destinations/the_trade_desk/data/TTDCommonConfig.json b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDCommonConfig.json similarity index 100% rename from src/cdk/v2/destinations/the_trade_desk/data/TTDCommonConfig.json rename to src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDCommonConfig.json diff --git a/src/cdk/v2/destinations/the_trade_desk/data/TTDItemConfig.json b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDItemConfig.json similarity index 100% rename from src/cdk/v2/destinations/the_trade_desk/data/TTDItemConfig.json rename to src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDItemConfig.json diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml new file mode 100644 index 0000000000..5191320cdc --- /dev/null +++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml @@ -0,0 +1,59 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + - name: defaultRequestConfig + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - path: ./config + exportAll: true + - path: ./utils + exportAll: true + +steps: + - name: validateConfig + template: | + $.assertConfig(.destination.Config.advertiserId, "Advertiser ID is not present. Aborting") + $.assertConfig(.destination.Config.trackerId, "Tracking Tag ID is not present. Aborting") + + - name: validateInput + template: | + let messageType = .message.type; + $.assert(messageType, "message Type is not present. Aborting."); + $.assert(messageType.toLowerCase() === $.EventType.TRACK, "Event type " + messageType + " is not supported"); + $.assert(.message.event, "Event is not present. Aborting."); + + - name: prepareTrackPayload + template: | + const configPayload = $.prepareFromConfig(.destination); + const commonPayload = $.prepareCommonPayload(.message); + const { id, type } = $.getAdvertisingId(.message); + const items = $.prepareItemsPayload(.message); + const customProperties = $.prepareCustomProperties(.message, .destination); + const eventName = $.populateEventName(.message, .destination); + const value = $.getRevenue(.message); + let payload = { + ...configPayload, + ...commonPayload, + event_name: eventName, + value, + items, + adid: id, + adid_type: type, + ...customProperties, + data_processing_option: $.getDataProcessingOptions(.message), + privacy_settings: $.getPrivacySetting(.message), + }; + payload = $.enrichTrackPayload(.message, payload); + payload; + + - name: buildResponseForProcessTransformation + template: | + const response = $.defaultRequestConfig(); + response.body.JSON = {data: [$.removeUndefinedAndNullValues($.outputs.prepareTrackPayload)]}; + response.endpoint = $.REAL_TIME_CONVERSION_ENDPOINT; + response.headers = { + "Content-Type": "application/json" + }; + response; diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js new file mode 100644 index 0000000000..2232be61f0 --- /dev/null +++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js @@ -0,0 +1,315 @@ +const lodash = require('lodash'); +const get = require('get-value'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { + constructPayload, + getHashFromArray, + isDefinedAndNotNull, + isAppleFamily, + getIntegrationsObj, + extractCustomFields, + generateExclusionList, +} = require('../../../../v0/util'); +const { + CONVERSION_SUPPORTED_ID_TYPES, + COMMON_CONFIGS, + ITEM_CONFIGS, + ECOMM_EVENT_MAP, +} = require('./config'); + +const prepareCommonPayload = (message) => constructPayload(message, COMMON_CONFIGS); + +const prepareFromConfig = (destination) => ({ + tracker_id: destination.Config?.trackerId, + adv: destination.Config?.advertiserId, +}); + +/** + * Calculates the revenue based on the given message. + * + * @param {Object} message - The message object containing the event and properties. + * @returns {number} - The calculated revenue. + * @throws {InstrumentationError} - If the event is 'Order Completed' and revenue is not provided. + */ +const getRevenue = (message) => { + const { event, properties } = message; + let revenue = properties?.value; + const eventsMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; + if (eventsMapInfo?.rootLevelPriceSupported) { + const { price, quantity = 1 } = properties; + if (price && !Number.isNaN(parseFloat(price)) && !Number.isNaN(parseInt(quantity, 10))) { + revenue = parseFloat(price) * parseInt(quantity, 10); + } + } else if (eventsMapInfo?.revenueFieldSupported) { + revenue = properties?.revenue || revenue; + if (event.toLowerCase() === 'order completed' && !revenue) { + throw new InstrumentationError('value is required for `Order Completed` event'); + } + } + + return revenue; +}; + +/** + * Generates items from properties of a given message. + * + * @param {Object} message - The message object containing properties. + * @returns {Array} - An array of items generated from the properties. + */ +const prepareItemsFromProperties = (message) => { + const { properties } = message; + const items = []; + const item = constructPayload(properties, ITEM_CONFIGS); + items.push(item); + return items; +}; + +/** + * Generates items payload from products. + * + * @param {Object} message - The message object. + * @returns {Array} - The items payload. + */ +const prepareItemsFromProducts = (message) => { + const products = get(message, 'properties.products'); + const items = []; + products.forEach((product) => { + const item = constructPayload(product, ITEM_CONFIGS); + const itemExclusionList = generateExclusionList(ITEM_CONFIGS); + extractCustomFields(product, item, 'root', itemExclusionList); + items.push(item); + }); + return items; +}; + +/** + * Generates items payload from root properties or products. + * + * @param {Object} message - The message object containing event and properties. + * @returns {Array} - The array of items payload. + */ +const prepareItemsPayload = (message) => { + const { event } = message; + let items; + const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; + if (eventMapInfo?.itemsArray) { + // if event is one of the supported ecommerce events and products array is present + items = prepareItemsFromProducts(message); + } else if (eventMapInfo) { + // if event is one of the supported ecommerce events and products array is not present + items = prepareItemsFromProperties(message); + } + return items; +}; + +/** + * Retrieves the device advertising ID and type based on the provided message. + * + * @param {Object} message - The message object containing the context. + * @returns {Object} - An object containing the device advertising ID and type. + */ +const getDeviceAdvertisingId = (message) => { + const { context } = message; + const deviceId = context?.device?.advertisingId; + const osName = context?.os?.name?.toLowerCase(); + + let type; + switch (osName) { + case 'android': + type = 'AAID'; + break; + case 'windows': + type = 'NAID'; + break; + default: + type = isAppleFamily(osName) ? 'IDFA' : undefined; + break; + } + + return { deviceId, type }; +}; + +/** + * Retrieves the external ID object from the given message context. + * + * @param {Object} message - The message object containing the context. + * @returns {Object|undefined} - The external ID object, or undefined if not found. + */ +const getDestinationExternalIDObject = (message) => { + const { context } = message; + const externalIdArray = context?.externalId || []; + + let externalIdObj; + + if (Array.isArray(externalIdArray)) { + externalIdObj = externalIdArray.find( + (extIdObj) => + CONVERSION_SUPPORTED_ID_TYPES.includes(extIdObj?.type?.toUpperCase()) && extIdObj?.id, + ); + } + return externalIdObj; +}; + +/** + * Retrieves the advertising ID and type from the given message. + * + * @param {Object} message - The message object containing the context. + * @returns {Object} - An object containing the advertising ID and type. + * If the advertising ID and type are found in the device context, they are returned. + * If not, the external ID object is checked and if found, its ID and type are returned. + * If neither the device context nor the external ID object contain the required information, + * an object with null values for ID and type is returned. + */ +const getAdvertisingId = (message) => { + const { deviceId, type } = getDeviceAdvertisingId(message); + if (deviceId && type) { + return { id: deviceId, type }; + } + const externalIdObj = getDestinationExternalIDObject(message); + if (externalIdObj?.id && externalIdObj?.type) { + return { id: externalIdObj.id, type: externalIdObj.type.toUpperCase() }; + } + + return { id: null, type: null }; +}; + +/** + * Prepares custom properties (td1-td10) for a given message and destination. + * + * @param {object} message - The message object. + * @param {object} destination - The destination object. + * @returns {object} - The prepared payload object. + */ +const prepareCustomProperties = (message, destination) => { + const { customProperties } = destination.Config; + const payload = {}; + if (customProperties) { + customProperties.forEach((customProperty) => { + const { rudderProperty, tradeDeskProperty } = customProperty; + const value = get(message, rudderProperty); + if (value) { + payload[tradeDeskProperty] = value; + // unset the rudder property from the message, since it is already mapped to a trade desk property + lodash.unset(message, rudderProperty); + } + }); + } + return payload; +}; + +/** + * Retrieves the event name based on the provided message and destination. + * + * @param {object} message - The message object containing the event. + * @param {object} destination - The destination object containing the events mapping configuration. + * @returns {string} - The event name. + */ +const populateEventName = (message, destination) => { + let eventName; + const { event } = message; + const { eventsMapping } = destination.Config; + + // if event is mapped on dashboard, use the mapped event name + if (Array.isArray(eventsMapping) && eventsMapping.length > 0) { + const keyMap = getHashFromArray(eventsMapping, 'from', 'to'); + eventName = keyMap[event.toLowerCase()]; + } + + if (eventName) { + return eventName; + } + + // if event is one of the supported ecommerce events, use the mapped trade desk event name + const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()]; + if (isDefinedAndNotNull(eventMapInfo)) { + return eventMapInfo.event; + } + + // else return the event name as it is + return event; +}; + +/** + * Retrieves the data processing options based on the provided message. + * + * @param {string} message - The message to process. + * @throws {InstrumentationError} - Throws an error if the region is not supported, if no policies are provided, if multiple policies are provided, or if the policy is not supported. + * @returns {Object} - The data processing options, including the policies and region. + */ +const getDataProcessingOptions = (message) => { + const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK') || {}; + let { policies } = integrationObj; + const { region } = integrationObj; + let dataProcessingOptions; + + if (region && !region.toLowerCase().startsWith('us')) { + throw new InstrumentationError('Only US states are supported'); + } + + if (!policies || (Array.isArray(policies) && policies.length === 0)) { + policies = ['LDU']; + } + + if (policies.length > 1) { + throw new InstrumentationError('Only one policy is allowed'); + } + + if (policies[0] !== 'LDU') { + throw new InstrumentationError('Only LDU policy is supported'); + } + + if (policies && region) { + dataProcessingOptions = { policies, region }; + } + + return dataProcessingOptions; +}; + +const getPrivacySetting = (message) => { + const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK'); + return integrationObj?.privacy_settings; +}; + +/** + * Enriches the track payload with extra properties present in 'properties' other than the ones defined in TTDCommonConfig.json and TTDItemConfig.json + * + * @param {Object} message - The message object containing the event information. + * @param {Object} payload - The payload object to be enriched. + * @returns {Object} - The enriched payload object. + */ +const enrichTrackPayload = (message, payload) => { + let rawPayload = { ...payload }; + const eventsMapInfo = ECOMM_EVENT_MAP[message.event.toLowerCase()]; + // checking if event is an ecomm one and itemsArray/products support is not present. e.g Product Added event + if (eventsMapInfo && !eventsMapInfo.itemsArray) { + const itemExclusionList = generateExclusionList(ITEM_CONFIGS); + rawPayload = extractCustomFields(message, rawPayload, ['properties'], itemExclusionList); + } else if (eventsMapInfo) { + // for ecomm events with products array supports. e.g Order Completed event + rawPayload = extractCustomFields( + message, + rawPayload, + ['properties'], + ['products', 'revenue', 'value'], + ); + } else { + // for custom events + rawPayload = extractCustomFields(message, rawPayload, ['properties'], ['value']); + } + return rawPayload; +}; + +module.exports = { + prepareFromConfig, + getRevenue, + prepareCommonPayload, + prepareItemsPayload, + getDeviceAdvertisingId, + getDestinationExternalIDObject, + getAdvertisingId, + prepareCustomProperties, + populateEventName, + getDataProcessingOptions, + getPrivacySetting, + enrichTrackPayload, +}; diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js new file mode 100644 index 0000000000..20b39fab93 --- /dev/null +++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js @@ -0,0 +1,621 @@ +const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { + getRevenue, + getDeviceAdvertisingId, + getDestinationExternalIDObject, + getAdvertisingId, + prepareCustomProperties, + populateEventName, + getDataProcessingOptions, + getPrivacySetting, + enrichTrackPayload, +} = require('./utils'); + +describe('getRevenue', () => { + it('should return revenue value from message properties for custom events', () => { + const message = { + event: 'customEvent', + properties: { + value: 100, + }, + }; + const result = getRevenue(message); + expect(result).toBe(100); + }); + + it('should calculate revenue based on price and quantity from message properties if ecomm event is supported for price calculation', () => { + const message = { + event: 'Product Added', + properties: { + price: 10, + quantity: 5, + }, + }; + const result = getRevenue(message); + expect(result).toBe(50); + }); + + it('should return revenue value from message properties if ecomm event is supported for revenue calculation', () => { + const message = { + event: 'Order Completed', + properties: { + revenue: 200, + }, + }; + const result = getRevenue(message); + expect(result).toBe(200); + }); + + it('should return default revenue value from properties.value for ecomm events', () => { + let message = { + event: 'Product Added', + properties: { + price: '', + value: 200, + }, + }; + let result = getRevenue(message); + expect(result).toBe(200); + + message = { + event: 'Order Completed', + properties: { + value: 200, + }, + }; + result = getRevenue(message); + expect(result).toBe(200); + }); + + it('should throw an Instrumentation error if revenue is missing for `Order Completed` event', () => { + const message = { + event: 'Order Completed', + properties: {}, + }; + expect(() => { + getRevenue(message); + }).toThrow(InstrumentationError); + }); +}); + +describe('getDeviceAdvertisingId', () => { + it('should return an object with deviceId and type properties when context.device.advertisingId and context.os.name are present', () => { + let message = { + context: { + device: { + advertisingId: '123456789', + }, + os: { + name: 'android', + }, + }, + }; + let result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: '123456789', type: 'AAID' }); + + message = { + context: { + device: { + advertisingId: '123456789', + }, + os: { + name: 'ios', + }, + }, + }; + result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: '123456789', type: 'IDFA' }); + + message = { + context: { + device: { + advertisingId: '123456789', + }, + os: { + name: 'windows', + }, + }, + }; + result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: '123456789', type: 'NAID' }); + }); + + it('should return an object with undefined type property when osName is not "android", "windows", or an Apple OS', () => { + const message = { + context: { + device: { + advertisingId: '123456789', + }, + os: { + name: 'linux', + }, + }, + }; + const result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: '123456789', type: undefined }); + }); + + it('should return an object with undefined deviceId and type properties when context is undefined', () => { + let message = {}; + let result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: undefined, type: undefined }); + + message = { + context: {}, + }; + result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: undefined, type: undefined }); + + message = { + context: { + device: {}, + }, + }; + result = getDeviceAdvertisingId(message); + expect(result).toEqual({ deviceId: undefined, type: undefined }); + }); +}); + +describe('getDestinationExternalIDObject', () => { + it('should return the external ID object when it exists in the message context', () => { + const message = { + context: { + externalId: [ + { id: '123', type: 'daid' }, + { id: '456', type: 'type123' }, + ], + }, + }; + const result = getDestinationExternalIDObject(message); + expect(result).toEqual({ id: '123', type: 'daid' }); + }); + + it('should return undefined when no external ID object exists in the message context', () => { + let message = { + context: { + externalId: [], + }, + }; + let result = getDestinationExternalIDObject(message); + expect(result).toBeUndefined(); + + message = { + context: {}, + }; + result = getDestinationExternalIDObject(message); + expect(result).toBeUndefined(); + }); + + it('should return the first matching external ID object in the array', () => { + const message = { + context: { + externalId: [ + { id: '', type: 'daid' }, + { id: '456', type: 'tdid' }, + { id: '789', type: 'UID2' }, + ], + }, + }; + const result = getDestinationExternalIDObject(message); + expect(result).toEqual({ id: '456', type: 'tdid' }); + }); +}); + +describe('getAdvertisingId', () => { + it('should return an object with the ID and type when the message contains a valid device advertising ID and OS type', () => { + const message = { + context: { + device: { + advertisingId: '1234567890', + }, + os: { + name: 'android', + }, + }, + }; + + const result = getAdvertisingId(message); + expect(result).toEqual({ id: '1234567890', type: 'AAID' }); + }); + + it('should return an object with the ID and type when the message contains a valid external ID object with a supported type', () => { + const message = { + context: { + externalId: [ + { + type: 'IDFA', + id: 'abcdefg', + }, + ], + }, + }; + + const result = getAdvertisingId(message); + expect(result).toEqual({ id: 'abcdefg', type: 'IDFA' }); + }); + + it('should return an object with undefined ID and type when the message contains a valid external ID object with an unsupported type', () => { + let message = { + context: { + externalId: [ + { + type: 'unsupported', + id: '1234567890', + }, + ], + }, + }; + + let result = getAdvertisingId(message); + expect(result).toEqual({ id: null, type: null }); + + message = { + context: { + device: { + advertisingId: '1234567890', + }, + }, + }; + + result = getAdvertisingId(message); + expect(result).toEqual({ id: null, type: null }); + }); + + it('should return an object with undefined ID and type when the message contains an external ID object with a supported type but no ID or missing externalId', () => { + let message = { + context: { + externalId: [ + { + type: 'IDFA', + }, + ], + }, + }; + let result = getAdvertisingId(message); + expect(result).toEqual({ id: null, type: null }); + + message = { + context: {}, + }; + result = getAdvertisingId(message); + expect(result).toEqual({ id: null, type: null }); + }); +}); + +describe('prepareCustomProperties', () => { + it('should return an empty object when customProperties is an empty array', () => { + const message = {}; + let destination = { Config: { customProperties: [] } }; + let result = prepareCustomProperties(message, destination); + expect(result).toEqual({}); + + destination = { Config: { customProperties: [{ rudderProperty: '', tradeDeskProperty: '' }] } }; + result = prepareCustomProperties(message, destination); + expect(result).toEqual({}); + + destination = { Config: { customProperties: undefined } }; + result = prepareCustomProperties(message, destination); + expect(result).toEqual({}); + }); + + it('should return an object with `tradeDeskProperty` as key and `rudderProperty` value as value when `rudderProperty` exists in message', () => { + const message = { + rudderProperty1: 'value1', + rudderProperty2: 'value2', + }; + const destination = { + Config: { + customProperties: [ + { + rudderProperty: 'rudderProperty1', + tradeDeskProperty: 'tradeDeskProperty1', + }, + { + rudderProperty: 'rudderProperty2', + tradeDeskProperty: 'tradeDeskProperty2', + }, + { + rudderProperty: 'rudderProperty3', + tradeDeskProperty: 'tradeDeskProperty3', + }, + ], + }, + }; + const result = prepareCustomProperties(message, destination); + expect(result).toEqual({ + tradeDeskProperty1: 'value1', + tradeDeskProperty2: 'value2', + }); + }); +}); + +describe('populateEventName', () => { + it('should return the eventName if it exists in the eventsMapping of destination.Config', () => { + const message = { event: 'someEvent' }; + const destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } }; + const result = populateEventName(message, destination); + expect(result).toBe('mappedEvent'); + }); + + it('should return the eventName if it exists in the ECOMM_EVENT_MAP', () => { + const message = { event: 'product added' }; + let destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } }; + let result = populateEventName(message, destination); + expect(result).toBe('addtocart'); + + destination = { Config: { eventsMapping: [] } }; + result = populateEventName(message, destination); + expect(result).toBe('addtocart'); + }); + + it('should return undefined if eventsMapping is an empty array', () => { + const message = { event: 'someEvent' }; + const destination = { Config: { eventsMapping: [] } }; + const result = populateEventName(message, destination); + expect(result).toBe('someEvent'); + }); +}); + +describe('getDataProcessingOptions', () => { + it('should return an object with policies and region when provided a integrationObj in message', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: ['LDU'], + region: 'US-CO', + }, + }, + }; + const expected = { + policies: ['LDU'], + region: 'US-CO', + }; + const result = getDataProcessingOptions(message); + expect(result).toEqual(expected); + }); + + it('should throw an InstrumentationError if the region is not a US state', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: ['LDU'], + region: 'EU-abc', + }, + }, + }; + expect(() => { + getDataProcessingOptions(message); + }).toThrow(InstrumentationError); + }); + + it('should throw an InstrumentationError if multiple policies are provided', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: ['LDU', 'Policy2'], + region: 'US-CO', + }, + }, + }; + + expect(() => { + getDataProcessingOptions(message); + }).toThrow(InstrumentationError); + }); + + it('should throw an InstrumentationError if a policy other than `LDU` is provided', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: ['Policy1'], + region: 'US-CO', + }, + }, + }; + + expect(() => { + getDataProcessingOptions(message); + }).toThrow(InstrumentationError); + }); + + it('should return an object with default policy `LDU` when policies are not provided', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + policies: [], + region: 'US-CO', + }, + }, + }; + + const expected = { + policies: ['LDU'], + region: 'US-CO', + }; + + expect(getDataProcessingOptions(message)).toEqual(expected); + }); + + it('should handle empty cases', () => { + let message = { + integrations: { + All: true, + THE_TRADE_DESK: {}, + }, + }; + + expect(getDataProcessingOptions(message)).toBeUndefined(); + + message = { + integrations: { + All: true, + }, + }; + + expect(getDataProcessingOptions(message)).toBeUndefined(); + + message = { + integrations: { + All: true, + THE_TRADE_DESK: { region: 'US-CO' }, + }, + }; + + expect(getDataProcessingOptions(message)).toEqual({ policies: ['LDU'], region: 'US-CO' }); + }); +}); + +describe('getPrivacySetting', () => { + it('should return the privacy settings object when it exists in the integration object', () => { + const message = { + integrations: { + All: true, + THE_TRADE_DESK: { + privacy_settings: [ + { + privacy_type: 'GDPR', + is_applicable: 1, + consent_string: 'ok', + }, + ], + }, + }, + }; + const expected = [ + { + privacy_type: 'GDPR', + is_applicable: 1, + consent_string: 'ok', + }, + ]; + const result = getPrivacySetting(message); + expect(result).toEqual(expected); + }); + + it('should return null when the privacy settings object does not exist in the integration object', () => { + let message = { integrations: {} }; + expect(getPrivacySetting(message)).toBeUndefined(); + + message = { integrations: { THE_TRADE_DESK: {} } }; + expect(getPrivacySetting(message)).toBeUndefined(); + + message = { integrations: { THE_TRADE_DESK: { privacy_settings: null } } }; + expect(getPrivacySetting(message)).toBeNull(); + }); +}); + +describe('enrichTrackPayload', () => { + it('should correctly enrich the payload with custom fields for ecomm events where product array is not supported', () => { + const message = { + event: 'Product Added', + properties: { + product_id: 'prd123', + sku: 'sku123', + brand: 'brand123', + property1: 'value1', + property2: 'value2', + }, + }; + const payload = { + items: [ + { + item_code: 'prd123', + }, + ], + property1: 'value1', + property2: 'value2', + }; + const expectedPayload = { + items: [ + { + item_code: 'prd123', + }, + ], + brand: 'brand123', + property1: 'value1', + property2: 'value2', + }; + + const result = enrichTrackPayload(message, payload); + expect(result).toEqual(expectedPayload); + }); + + it('should correctly enrich the payload with custom fields when the for ecomm events with products array support', () => { + const message = { + event: 'order completed', + properties: { + order_id: 'ord123', + total: 52.0, + subtotal: 45.0, + revenue: 50.0, + products: [{ product_id: 'prd123', sku: 'sku123', brand: 'brand123' }], + property1: 'value1', + property2: 'value2', + }, + }; + const payload = { + order_id: 'ord123', + value: 50.0, + items: [{ item_code: 'prd123', brand: 'brand123' }], + property1: 'value1', + property2: 'value2', + }; + const expectedPayload = { + order_id: 'ord123', + total: 52.0, + subtotal: 45.0, + value: 50.0, + items: [{ item_code: 'prd123', brand: 'brand123' }], + property1: 'value1', + property2: 'value2', + }; + + const result = enrichTrackPayload(message, payload); + + expect(result).toEqual(expectedPayload); + }); + + it('should return the enriched payload for custom event', () => { + const message = { + event: 'someEvent', + properties: { + order_id: 'ord123', + property1: 'value1', + property2: 'value2', + revenue: 10, + value: 11, + products: [ + { + product_id: 'prd123', + test: 'test', + }, + ], + }, + }; + const payload = { + order_id: 'ord123', + value: 11, + }; + const expectedPayload = { + order_id: 'ord123', + property1: 'value1', + property2: 'value2', + revenue: 10, + value: 11, + products: [ + { + product_id: 'prd123', + test: 'test', + }, + ], + }; + + const result = enrichTrackPayload(message, payload); + expect(result).toEqual(expectedPayload); + }); +}); diff --git a/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml index cd84ecbc87..5862fbf372 100644 --- a/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml +++ b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml @@ -1,4 +1,3 @@ - bindings: - name: EventType path: ../../../../constants @@ -48,7 +47,6 @@ steps: "action": $.ACTION_MAP[action], })[] - - name: buildResponseForProcessTransformation description: build response template: | diff --git a/src/cdk/v2/destinations/zapier/procWorkflow.yaml b/src/cdk/v2/destinations/zapier/procWorkflow.yaml index 82d15cdec0..9f1512836e 100644 --- a/src/cdk/v2/destinations/zapier/procWorkflow.yaml +++ b/src/cdk/v2/destinations/zapier/procWorkflow.yaml @@ -44,4 +44,3 @@ steps: "content-type": "application/json" }; response - diff --git a/src/cdk/v2/handler.ts b/src/cdk/v2/handler.ts index 47d6d10179..edd14e7298 100644 --- a/src/cdk/v2/handler.ts +++ b/src/cdk/v2/handler.ts @@ -50,16 +50,20 @@ export async function getWorkflowEngine( const workflowEnginePromiseMap = new Map(); -export function getCachedWorkflowEngine( +export async function getCachedWorkflowEngine( destName: string, feature: string, bindings: Record = {}, -): WorkflowEngine { +): Promise { // Create a new instance of the engine for the destination if needed // TODO: Use cache to avoid long living engine objects workflowEnginePromiseMap[destName] = workflowEnginePromiseMap[destName] || new Map(); if (!workflowEnginePromiseMap[destName][feature]) { - workflowEnginePromiseMap[destName][feature] = getWorkflowEngine(destName, feature, bindings); + workflowEnginePromiseMap[destName][feature] = await getWorkflowEngine( + destName, + feature, + bindings, + ); } return workflowEnginePromiseMap[destName][feature]; } @@ -97,5 +101,8 @@ export function executeStep( ): Promise { return workflowEngine .getStepExecutor(stepName) - .execute(input, Object.assign(workflowEngine.bindings, getEmptyExecutionBindings(), bindings)); + .execute( + input, + Object.assign(workflowEngine.getBindings(), getEmptyExecutionBindings(), bindings), + ); } diff --git a/src/controllers/destination.ts b/src/controllers/destination.ts index 71075d1b4c..d8b3c94524 100644 --- a/src/controllers/destination.ts +++ b/src/controllers/destination.ts @@ -15,6 +15,7 @@ import logger from '../logger'; import { getIntegrationVersion } from '../util/utils'; import tags from '../v0/util/tags'; import { DynamicConfigParser } from '../util/dynamicConfigParser'; +import { checkInvalidRtTfEvents } from '../v0/util'; export class DestinationController { public static async destinationTransformAtProcessor(ctx: Context) { @@ -101,6 +102,20 @@ export class DestinationController { const routerRequest = ctx.request.body as RouterTransformationRequest; const destination = routerRequest.destType; let events = routerRequest.input; + const errorRespEvents = checkInvalidRtTfEvents(events); + if (errorRespEvents.length > 0) { + errorRespEvents[0].metadata = [ + { + destType: destination, + }, + ]; + logger.debug( + `[${destination}] Invalid router transform payload structure: ${JSON.stringify(events)}`, + ); + ctx.body = { output: errorRespEvents }; + ControllerUtility.postProcess(ctx); + return ctx; + } const metaTags = MiscService.getMetaTags(events[0].metadata); stats.histogram('dest_transform_input_events', events.length, { destination, diff --git a/src/controllers/obs.delivery.js b/src/controllers/obs.delivery.js index 4a93afe1dc..5aa3ca5862 100644 --- a/src/controllers/obs.delivery.js +++ b/src/controllers/obs.delivery.js @@ -96,11 +96,15 @@ const DestProxyController = { destination, }); - response = generateErrorObject(err, { - [tags.TAG_NAMES.DEST_TYPE]: destination.toUpperCase(), - [tags.TAG_NAMES.MODULE]: tags.MODULES.DESTINATION, - [tags.TAG_NAMES.FEATURE]: tags.FEATURES.DATA_DELIVERY, - }, false); + response = generateErrorObject( + err, + { + [tags.TAG_NAMES.DEST_TYPE]: destination.toUpperCase(), + [tags.TAG_NAMES.MODULE]: tags.MODULES.DESTINATION, + [tags.TAG_NAMES.FEATURE]: tags.FEATURES.DATA_DELIVERY, + }, + false, + ); response.message = `[TransformerProxyTest] Error occurred while testing proxy for destination ("${destination}"): "${err.message}"`; logger.error(response.message); logger.error(err); diff --git a/src/controllers/util/index.ts b/src/controllers/util/index.ts index 75d3d8ffa7..c5bf7ab358 100644 --- a/src/controllers/util/index.ts +++ b/src/controllers/util/index.ts @@ -51,7 +51,7 @@ export class ControllerUtility { private static convertSourceInputv0Tov1(sourceEvents: unknown[]): SourceInput[] { return sourceEvents.map( - (sourceEvent) => ({ event: sourceEvent, source: undefined } as SourceInput), + (sourceEvent) => ({ event: sourceEvent, source: undefined }) as SourceInput, ); } diff --git a/src/legacy/router.js b/src/legacy/router.js index f8deb3fe62..9dd83b5988 100644 --- a/src/legacy/router.js +++ b/src/legacy/router.js @@ -7,7 +7,7 @@ const Router = require('@koa/router'); const lodash = require('lodash'); const fs = require('fs'); const path = require('path'); -const { PlatformError } = require('@rudderstack/integrations-lib'); +const { PlatformError, getErrorRespEvents } = require('@rudderstack/integrations-lib'); const logger = require('../logger'); const stats = require('../util/stats'); const { SUPPORTED_VERSIONS, API_VERSION } = require('../routes/utils/constants'); @@ -18,7 +18,6 @@ const { isNonFuncObject, getMetadata, generateErrorObject, - getErrorRespEvents, isCdkDestination, checkAndCorrectUserId, } = require('../v0/util'); diff --git a/src/services/destination/nativeIntegration.ts b/src/services/destination/nativeIntegration.ts index 2dd78b58e2..c33772d01d 100644 --- a/src/services/destination/nativeIntegration.ts +++ b/src/services/destination/nativeIntegration.ts @@ -212,7 +212,7 @@ export class NativeIntegrationDestinationService implements DestinationService { error: JSON.stringify(v0Response.destinationResponse?.response), statusCode: v0Response.status, metadata, - } as DeliveryJobState), + }) as DeliveryJobState, ); responseProxy = { response: jobStates, diff --git a/src/services/userTransform.ts b/src/services/userTransform.ts index bf34e3d82a..bae833c86a 100644 --- a/src/services/userTransform.ts +++ b/src/services/userTransform.ts @@ -158,7 +158,7 @@ export class UserTransformService { statusCode: status, metadata: e.metadata, error: errorString, - } as ProcessorTransformationResponse), + }) as ProcessorTransformationResponse, ), ); stats.counter('user_transform_errors', eventsToProcess.length, { 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/src/util/customTransformer-v1.js b/src/util/customTransformer-v1.js index 60f8e493fa..7e854a3714 100644 --- a/src/util/customTransformer-v1.js +++ b/src/util/customTransformer-v1.js @@ -55,7 +55,7 @@ async function userTransformHandlerV1( testMode = false, ) { if (!userTransformation.versionId) { - return { transformedEvents : events }; + return { transformedEvents: events }; } const isolatevmFactory = await getFactory( @@ -88,9 +88,9 @@ async function userTransformHandlerV1( const tags = { identifier: 'v1', errored: transformationError ? true : false, - ...events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}, - ...events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {} - } + ...(events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}), + ...(events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}), + }; stats.counter('user_transform_function_input_events', events.length, tags); stats.timing('user_transform_function_latency', invokeTime, tags); } diff --git a/src/util/customTransformer.js b/src/util/customTransformer.js index 001fe3216c..a87c12dd6e 100644 --- a/src/util/customTransformer.js +++ b/src/util/customTransformer.js @@ -254,9 +254,9 @@ async function runUserTransform( const tags = { identifier: 'v0', errored: transformationError ? true : false, - ...events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}, - ...events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {} - } + ...(events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}), + ...(events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}), + }; stats.counter('user_transform_function_input_events', events.length, tags); stats.timing('user_transform_function_latency', invokeTime, tags); diff --git a/src/util/customTransformerFactory.js b/src/util/customTransformerFactory.js index 1bf10e5d45..ee53531946 100644 --- a/src/util/customTransformerFactory.js +++ b/src/util/customTransformerFactory.js @@ -1,12 +1,6 @@ -const { - setOpenFaasUserTransform, - runOpenFaasUserTransform, -} = require('./customTransformer-faas'); +const { setOpenFaasUserTransform, runOpenFaasUserTransform } = require('./customTransformer-faas'); -const { - userTransformHandlerV1, - setUserTransformHandlerV1, -} = require('./customTransformer-v1'); +const { userTransformHandlerV1, setUserTransformHandlerV1 } = require('./customTransformer-v1'); const UserTransformHandlerFactory = (userTransformation) => { return { @@ -23,20 +17,10 @@ const UserTransformHandlerFactory = (userTransformation) => { switch (userTransformation.language) { case 'pythonfaas': case 'python': - return runOpenFaasUserTransform( - events, - userTransformation, - libraryVersionIds, - testMode - ); + return runOpenFaasUserTransform(events, userTransformation, libraryVersionIds, testMode); default: - return userTransformHandlerV1( - events, - userTransformation, - libraryVersionIds, - testMode - ); + return userTransformHandlerV1(events, userTransformation, libraryVersionIds, testMode); } }, }; diff --git a/src/util/error-extractor/index.ts b/src/util/error-extractor/index.ts index 68ebac9aca..6ff374b869 100644 --- a/src/util/error-extractor/index.ts +++ b/src/util/error-extractor/index.ts @@ -1,19 +1,18 @@ /* eslint-disable max-classes-per-file */ -import { MessageDetails, StatusCode, Stat } from "./types"; +import { MessageDetails, StatusCode, Stat } from './types'; export class ErrorDetailsExtractor { status: StatusCode; messageDetails: MessageDetails; - stat : Stat + stat: Stat; - constructor (builder: ErrorDetailsExtractorBuilder) { + constructor(builder: ErrorDetailsExtractorBuilder) { this.status = builder.getStatus(); this.messageDetails = builder.getMessageDetails(); this.stat = builder.getStat(); } - } export class ErrorDetailsExtractorBuilder { @@ -22,27 +21,28 @@ export class ErrorDetailsExtractorBuilder { messageDetails: MessageDetails; stat: Stat; + constructor() { this.status = 0; this.messageDetails = {}; this.stat = {}; } - + setStatus(status: number): ErrorDetailsExtractorBuilder { this.status = status; return this; } setStat(stat: Record): ErrorDetailsExtractorBuilder { - this.stat = stat + this.stat = stat; return this; } /** * This means we need to set a message from a specific field that we see from the destination's response - * + * * @param {string} fieldPath -- Path of the field which should be set as "error message" - * @returns + * @returns */ setMessageField(fieldPath: string): ErrorDetailsExtractorBuilder { if (this.messageDetails?.message) { @@ -50,16 +50,16 @@ export class ErrorDetailsExtractorBuilder { return this; } this.messageDetails = { - field: fieldPath - } + field: fieldPath, + }; return this; } /** * This means we need to set the message provided - * + * * @param {string} msg - error message - * @returns + * @returns */ setMessage(msg: string): ErrorDetailsExtractorBuilder { if (this.messageDetails?.field) { @@ -67,13 +67,13 @@ export class ErrorDetailsExtractorBuilder { return this; } this.messageDetails = { - message: msg - } + message: msg, + }; return this; } build(): ErrorDetailsExtractor { - return new ErrorDetailsExtractor(this) + return new ErrorDetailsExtractor(this); } getStatus(): number { @@ -83,10 +83,8 @@ export class ErrorDetailsExtractorBuilder { getStat(): Record { return this.stat; } - + getMessageDetails(): Record { return this.messageDetails; } } - - diff --git a/src/util/error-extractor/types.ts b/src/util/error-extractor/types.ts index ff7290b4ff..b93d2fafe5 100644 --- a/src/util/error-extractor/types.ts +++ b/src/util/error-extractor/types.ts @@ -1,3 +1,3 @@ export type MessageDetails = Record; export type StatusCode = number; -export type Stat = Record \ No newline at end of file +export type Stat = Record; diff --git a/src/util/ivmFactory.js b/src/util/ivmFactory.js index 2ab5f9548a..9a6419295d 100644 --- a/src/util/ivmFactory.js +++ b/src/util/ivmFactory.js @@ -23,7 +23,9 @@ async function evaluateModule(isolate, context, moduleCode) { } async function loadModule(isolateInternal, contextInternal, moduleName, moduleCode) { - const module = await isolateInternal.compileModule(moduleCode, { filename: `library ${moduleName}` }); + const module = await isolateInternal.compileModule(moduleCode, { + filename: `library ${moduleName}`, + }); await module.instantiate(contextInternal, () => {}); return module; } @@ -256,7 +258,7 @@ async function createIvm(code, libraryVersionIds, versionId, secrets, testMode) } }); - await jail.set('extractStackTrace', function(trace, stringLiterals) { + await jail.set('extractStackTrace', function (trace, stringLiterals) { return extractStackTraceUptoLastSubstringMatch(trace, stringLiterals); }); @@ -346,7 +348,9 @@ async function createIvm(code, libraryVersionIds, versionId, secrets, testMode) // Now we can execute the script we just compiled: const bootstrapScriptResult = await bootstrap.run(context); // const customScript = await isolate.compileScript(`${library} ;\n; ${code}`); - const customScriptModule = await isolate.compileModule(`${codeWithWrapper}`, { filename: 'base transformation' }); + const customScriptModule = await isolate.compileModule(`${codeWithWrapper}`, { + filename: 'base transformation', + }); await customScriptModule.instantiate(context, async (spec) => { if (librariesMap[spec]) { return compiledModules[spec].module; diff --git a/src/util/prometheus.js b/src/util/prometheus.js index d7ba3b7c61..eec480bbff 100644 --- a/src/util/prometheus.js +++ b/src/util/prometheus.js @@ -533,7 +533,15 @@ class Prometheus { name: 'outgoing_request_count', help: 'Outgoing HTTP requests count', type: 'counter', - labelNames: ['feature', 'destType', 'endpointPath', 'success', 'statusCode'], + labelNames: [ + 'feature', + 'destType', + 'endpointPath', + 'success', + 'statusCode', + 'requestMethod', + 'module', + ], }, // Gauges @@ -573,7 +581,7 @@ class Prometheus { name: 'outgoing_request_latency', help: 'Outgoing HTTP requests duration in seconds', type: 'histogram', - labelNames: ['feature', 'destType', 'endpointPath'], + labelNames: ['feature', 'destType', 'endpointPath', 'requestMethod', 'module'], }, { name: 'http_request_duration', diff --git a/src/util/stats.js b/src/util/stats.js index e57ab85731..9a32fd1de3 100644 --- a/src/util/stats.js +++ b/src/util/stats.js @@ -13,17 +13,19 @@ function init() { switch (statsClientType) { case 'statsd': - logger.info("setting up statsd client") + logger.info('setting up statsd client'); statsClient = new statsd.Statsd(); break; case 'prometheus': - logger.info("setting up prometheus client") + logger.info('setting up prometheus client'); statsClient = new prometheus.Prometheus(); break; default: - logger.error(`invalid stats client type: ${statsClientType}, supported values are 'statsd' and 'prometheues'`) + logger.error( + `invalid stats client type: ${statsClientType}, supported values are 'statsd' and 'prometheues'`, + ); } } diff --git a/src/v0/destinations/active_campaign/transform.js b/src/v0/destinations/active_campaign/transform.js index 981dbd7520..3978f868b1 100644 --- a/src/v0/destinations/active_campaign/transform.js +++ b/src/v0/destinations/active_campaign/transform.js @@ -62,6 +62,8 @@ const syncContact = async (contactPayload, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: endPoint, + requestMethod: 'POST', + module: 'router', }); if (res.success === false) { errorHandler(res, 'Failed to create new contact'); @@ -129,6 +131,8 @@ const customTagProcessor = async (message, category, destination, contactId) => destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/tags`, + requestMethod: 'GET', + module: 'router', }); promises.push(resp); } @@ -253,6 +257,8 @@ const customFieldProcessor = async (message, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/fields`, + requestMethod: 'GET', + module: 'router', }); promises.push(resp); } @@ -351,6 +357,8 @@ const customListProcessor = async (message, category, destination, contactId) => destType: 'active_campaign', feature: 'transformation', endpointPath: mergeListWithContactUrl, + requestMethod: 'POST', + module: 'router', }); promises.push(res); } @@ -409,6 +417,8 @@ const screenRequestHandler = async (message, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/eventTrackingEvents`, + requestMethod: 'GET', + module: 'router', }); if (res.success === false) { errorHandler(res, 'Failed to retrieve events'); @@ -473,6 +483,8 @@ const trackRequestHandler = async (message, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/eventTrackingEvents`, + requestMethod: 'GET', + module: 'router', }); if (res.success === false) { diff --git a/src/v0/destinations/adobe_analytics/utils.js b/src/v0/destinations/adobe_analytics/utils.js index 97dc6e90bb..ceba177ff1 100644 --- a/src/v0/destinations/adobe_analytics/utils.js +++ b/src/v0/destinations/adobe_analytics/utils.js @@ -93,7 +93,7 @@ function escapeToHTML(inputString) { '&': '&', '<': '<', '>': '>', - }[match]), + })[match], ); } diff --git a/src/v0/destinations/af/deleteUsers.js b/src/v0/destinations/af/deleteUsers.js index bb711292c0..ab515642aa 100644 --- a/src/v0/destinations/af/deleteUsers.js +++ b/src/v0/destinations/af/deleteUsers.js @@ -39,6 +39,8 @@ const deleteUser = async (config, endpoint, body, identityType, identityValue) = destType: 'af', feature: 'deleteUsers', endpointPath: `appsflyer.com/api/gdpr/v1/opendsr_requests`, + requestMethod: 'POST', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(response); @@ -48,6 +50,7 @@ const deleteUser = async (config, endpoint, body, identityType, identityValue) = handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/af/transform.js b/src/v0/destinations/af/transform.js index 4d7ed7e635..d6c41937a1 100644 --- a/src/v0/destinations/af/transform.js +++ b/src/v0/destinations/af/transform.js @@ -113,9 +113,20 @@ function getEventValueForUnIdentifiedTrackEvent(message) { return { eventValue }; } -function getEventValueMapFromMappingJson(message, mappingJson, isMultiSupport) { +function getEventValueMapFromMappingJson( + message, + mappingJson, + isMultiSupport, + addPropertiesAtRoot, +) { let eventValue = {}; - set(eventValue, 'properties', message.properties); + + if (addPropertiesAtRoot) { + eventValue = message.properties; + } else { + set(eventValue, 'properties', message.properties); + } + const sourceKeys = Object.keys(mappingJson); sourceKeys.forEach((sourceKey) => { set(eventValue, mappingJson[sourceKey], get(message, sourceKey)); @@ -160,7 +171,7 @@ function processNonTrackEvents(message, eventName) { return payload; } -function processEventTypeTrack(message) { +function processEventTypeTrack(message, addPropertiesAtRoot) { let isMultiSupport = true; const evType = message.event && message.event.toLowerCase(); let category = ConfigCategory.DEFAULT; @@ -184,6 +195,7 @@ function processEventTypeTrack(message) { message, mappingConfig[category.name], isMultiSupport, + addPropertiesAtRoot, ); payload.eventName = message.event; payload.eventCurrency = message?.properties?.currency; @@ -196,7 +208,7 @@ function processSingleMessage(message, destination) { let payload; switch (messageType) { case EventType.TRACK: { - payload = processEventTypeTrack(message); + payload = processEventTypeTrack(message, destination.Config.addPropertiesAtRoot); break; } case EventType.SCREEN: { diff --git a/src/v0/destinations/am/config.js b/src/v0/destinations/am/config.js index 3e51a67137..78f8d43e94 100644 --- a/src/v0/destinations/am/config.js +++ b/src/v0/destinations/am/config.js @@ -136,5 +136,5 @@ module.exports = { batchEventsWithUserIdLengthLowerThanFive, IDENTIFY_AM, AMBatchSizeLimit, - AMBatchEventLimit + AMBatchEventLimit, }; diff --git a/src/v0/destinations/am/deleteUsers.js b/src/v0/destinations/am/deleteUsers.js index 6de9cf64a1..96c4f7b19c 100644 --- a/src/v0/destinations/am/deleteUsers.js +++ b/src/v0/destinations/am/deleteUsers.js @@ -43,6 +43,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'am', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status)) { @@ -51,6 +53,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js index 911ec51be0..afd72b77e1 100644 --- a/src/v0/destinations/am/transform.js +++ b/src/v0/destinations/am/transform.js @@ -22,13 +22,13 @@ const { getFieldValueFromMessage, getValueFromMessage, deleteObjectProperty, - getErrorRespEvents, removeUndefinedAndNullValues, isDefinedAndNotNull, isAppleFamily, isDefinedAndNotNullAndNotEmpty, simpleProcessRouterDest, isValidInteger, + handleRtTfSingleEventError, } = require('../../util'); const { BASE_URL, @@ -40,7 +40,6 @@ const { AMBatchSizeLimit, AMBatchEventLimit, } = require('./config'); -const tags = require('../../util/tags'); const AMUtils = require('./utils'); @@ -615,16 +614,16 @@ const processSingleMessage = (message, destination) => { case EventType.PAGE: if (useUserDefinedPageEventName) { const getMessagePath = userProvidedPageEventString - .substring( + ?.substring( userProvidedPageEventString.indexOf('{') + 2, userProvidedPageEventString.indexOf('}'), ) .trim(); evType = - userProvidedPageEventString.trim() === '' + userProvidedPageEventString?.trim() === '' ? name : userProvidedPageEventString - .trim() + ?.trim() .replaceAll(/{{([^{}]+)}}/g, get(message, getMessagePath)); } else { evType = `Viewed ${name || get(message, CATEGORY_KEY) || ''} Page`; @@ -702,6 +701,7 @@ const processSingleMessage = (message, destination) => { logger.debug('could not determine type'); throw new InstrumentationError('message type not supported'); } + AMUtils.validateEventType(evType); return responseBuilderSimple( groupInfo, payloadObjectName, @@ -904,16 +904,10 @@ const batch = (destEvents) => { // this case shold not happen and should be filtered already // by the first pass of single event transformation if (messageEvent && !userId && !deviceId) { - const errorResponse = getErrorRespEvents( - metadata, - 400, + const MissingUserIdDeviceIdError = new InstrumentationError( 'Both userId and deviceId cannot be undefined', - { - [tags.TAG_NAMES.ERROR_CATEGORY]: tags.ERROR_CATEGORIES.DATA_VALIDATION, - [tags.TAG_NAMES.ERROR_TYPE]: tags.ERROR_TYPES.INSTRUMENTATION, - }, ); - respList.push(errorResponse); + respList.push(handleRtTfSingleEventError(ev, MissingUserIdDeviceIdError, {})); return; } /* check if not a JSON body or (userId length < 5 && batchEventsWithUserIdLengthLowerThanFive is false) or diff --git a/src/v0/destinations/am/util.test.js b/src/v0/destinations/am/util.test.js index faaa9170f0..fa30a74757 100644 --- a/src/v0/destinations/am/util.test.js +++ b/src/v0/destinations/am/util.test.js @@ -1,4 +1,4 @@ -const { getUnsetObj } = require('./utils'); +const { getUnsetObj, validateEventType } = require('./utils'); describe('getUnsetObj', () => { it("should return undefined when 'message.integrations.Amplitude.fieldsToUnset' is not array", () => { @@ -64,3 +64,36 @@ describe('getUnsetObj', () => { expect(result).toBeUndefined(); }); }); + +describe('validateEventType', () => { + it('should validate event type when it is valid with only page name given', () => { + expect(() => { + validateEventType('Home Page'); + }).not.toThrow(); + }); + + it('should throw an error when event type is null', () => { + expect(() => { + validateEventType(null); + }).toThrow( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); + }); + + it('should throw an error when event type is undefined', () => { + expect(() => { + validateEventType(undefined); + }).toThrow( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); + }); + + // Function receives an empty string as event type + it('should throw an error when event type is an empty string', () => { + expect(() => { + validateEventType(''); + }).toThrow( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); + }); +}); diff --git a/src/v0/destinations/am/utils.js b/src/v0/destinations/am/utils.js index 71fe0ab459..ed1b772fca 100644 --- a/src/v0/destinations/am/utils.js +++ b/src/v0/destinations/am/utils.js @@ -10,6 +10,7 @@ // populate these dest keys const get = require('get-value'); const uaParser = require('@amplitude/ua-parser-js'); +const { InstrumentationError } = require('@rudderstack/integrations-lib'); const logger = require('../../../logger'); const { isDefinedAndNotNull } = require('../../util'); @@ -108,6 +109,19 @@ const getUnsetObj = (message) => { return unsetObject; }; + +/** + * Check for evType as in some cases, like when the page name is absent, + * either the template depends only on the event.name or there is no template provided by user + * @param {*} evType + */ +const validateEventType = (evType) => { + if (!isDefinedAndNotNull(evType) || (typeof evType === 'string' && evType.length === 0)) { + throw new InstrumentationError( + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + ); + } +}; module.exports = { getOSName, getOSVersion, @@ -117,4 +131,5 @@ module.exports = { getBrand, getEventId, getUnsetObj, + validateEventType, }; diff --git a/src/v0/destinations/bqstream/transform.js b/src/v0/destinations/bqstream/transform.js index 598a97946d..8ee96aecf1 100644 --- a/src/v0/destinations/bqstream/transform.js +++ b/src/v0/destinations/bqstream/transform.js @@ -5,7 +5,6 @@ const { EventType } = require('../../../constants'); const { defaultBatchRequestConfig, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, groupEventsByType, } = require('../../util'); @@ -130,10 +129,6 @@ const processEachTypedEventList = ( }; const processRouterDest = (inputs) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs, DESTINATION); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const finalResp = []; const batchedEvents = groupEventsByType(inputs); diff --git a/src/v0/destinations/braze/braze.util.test.js b/src/v0/destinations/braze/braze.util.test.js index 9e82a235f1..7b6a93d359 100644 --- a/src/v0/destinations/braze/braze.util.test.js +++ b/src/v0/destinations/braze/braze.util.test.js @@ -305,7 +305,14 @@ describe('dedup utility tests', () => { }, timeout: 10000, }, - { destType: 'braze', feature: 'transformation' }, + { + destType: 'braze', + feature: 'transformation', + endpointPath: '/users/export/ids', + feature: 'transformation', + module: 'router', + requestMethod: 'POST', + }, ); }); diff --git a/src/v0/destinations/braze/deleteUsers.js b/src/v0/destinations/braze/deleteUsers.js index b94d901138..33c0f2ef7f 100644 --- a/src/v0/destinations/braze/deleteUsers.js +++ b/src/v0/destinations/braze/deleteUsers.js @@ -22,7 +22,7 @@ const userDeletionHandler = async (userAttributes, config) => { // Endpoints different for different data centers. // DOC: https://www.braze.com/docs/user_guide/administrative/access_braze/braze_instances/ let endPoint; - const endpointPath = '/users/delete'; // TODO: to handle for destinations dynamically by extracting from endpoint + const endpointPath = '/users/delete'; const dataCenterArr = dataCenter.trim().split('-'); if (dataCenterArr[0].toLowerCase() === 'eu') { endPoint = 'https://rest.fra-01.braze.eu/users/delete'; @@ -46,6 +46,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'braze', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) { @@ -54,6 +56,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/braze/transform.js b/src/v0/destinations/braze/transform.js index 6549f5658f..d45640272e 100644 --- a/src/v0/destinations/braze/transform.js +++ b/src/v0/destinations/braze/transform.js @@ -223,6 +223,9 @@ async function processIdentify(message, destination) { { destType: 'braze', feature: 'transformation', + requestMethod: 'POST', + module: 'router', + endpointPath: '/users/identify', }, ); if (!isHttpStatusSuccess(brazeIdentifyResp.status)) { diff --git a/src/v0/destinations/braze/util.js b/src/v0/destinations/braze/util.js index 40b9a7eada..5f1f1e6205 100644 --- a/src/v0/destinations/braze/util.js +++ b/src/v0/destinations/braze/util.js @@ -163,6 +163,9 @@ const BrazeDedupUtility = { { destType: 'braze', feature: 'transformation', + requestMethod: 'POST', + module: 'router', + endpointPath: '/users/export/ids', }, ); stats.counter('braze_lookup_failure_count', 1, { diff --git a/src/v0/destinations/campaign_manager/transform.js b/src/v0/destinations/campaign_manager/transform.js index 3b480dbac2..14bc6d2c19 100644 --- a/src/v0/destinations/campaign_manager/transform.js +++ b/src/v0/destinations/campaign_manager/transform.js @@ -9,7 +9,6 @@ const { removeUndefinedAndNullValues, getSuccessRespEvents, isDefinedAndNotNull, - checkInvalidRtTfEvents, handleRtTfSingleEventError, getAccessToken, } = require('../../util'); @@ -245,11 +244,6 @@ const batchEvents = (eventChunksArray) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const batchErrorRespList = []; const eventChunksArray = []; const { destination } = inputs[0]; diff --git a/src/v0/destinations/canny/util.js b/src/v0/destinations/canny/util.js index f514a01e5c..1d03eed4b9 100644 --- a/src/v0/destinations/canny/util.js +++ b/src/v0/destinations/canny/util.js @@ -46,6 +46,8 @@ const retrieveUserId = async (apiKey, message) => { destType: 'canny', feature: 'transformation', endpointPath: `/v1/users/retrieve`, + requestMethod: 'POST', + module: 'processor', }, ); logger.debug(response); diff --git a/src/v0/destinations/clevertap/deleteUsers.js b/src/v0/destinations/clevertap/deleteUsers.js index 3c07a63d93..52119bf0f1 100644 --- a/src/v0/destinations/clevertap/deleteUsers.js +++ b/src/v0/destinations/clevertap/deleteUsers.js @@ -53,6 +53,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'clevertap', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(deletionResponse); @@ -62,6 +64,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/clevertap/transform.js b/src/v0/destinations/clevertap/transform.js index efcd101668..51ed5c851e 100644 --- a/src/v0/destinations/clevertap/transform.js +++ b/src/v0/destinations/clevertap/transform.js @@ -22,7 +22,6 @@ const { handleRtTfSingleEventError, batchMultiplexedEvents, getSuccessRespEvents, - checkInvalidRtTfEvents, } = require('../../util'); const { generateClevertapBatchedPayload } = require('./utils'); @@ -83,16 +82,21 @@ const responseWrapper = (payload, destination) => { } * } - * This function stringify the payload attributes if it's an array or objects. + * This function stringify the payload attributes if it's an array or objects. The keys that are not stringified are present in the `stringifyExcludeList` array. * @param {*} payload * @returns * return the final payload after converting to the relevant data-types. */ const convertObjectAndArrayToString = (payload, event) => { const finalPayload = {}; + const stringifyExcludeList = ['category-unsubscribe', 'category-resubscribe']; if (payload) { Object.keys(payload).forEach((key) => { - if (payload[key] && (Array.isArray(payload[key]) || typeof payload[key] === 'object')) { + if ( + payload[key] && + (Array.isArray(payload[key]) || typeof payload[key] === 'object') && + !stringifyExcludeList.includes(key) + ) { finalPayload[key] = JSON.stringify(payload[key]); } else { finalPayload[key] = payload[key]; @@ -384,13 +388,6 @@ const processEvent = (message, destination) => { const process = (event) => processEvent(event.message, event.destination); const processRouterDest = (inputs, reqMetadata) => { - // const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); - // return respList; - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const eventsChunk = []; const errorRespList = []; // const { destination } = inputs[0]; diff --git a/src/v0/destinations/clickup/util.js b/src/v0/destinations/clickup/util.js index 148fe1bd07..74e961906c 100644 --- a/src/v0/destinations/clickup/util.js +++ b/src/v0/destinations/clickup/util.js @@ -217,6 +217,9 @@ const retrieveCustomFields = async (listId, apiToken) => { const customFieldsResponse = await httpGET(endpoint, requestOptions, { destType: 'clickup', feature: 'transformation', + endpointPath: '/list/listId/field', + requestMethod: 'GET', + module: 'router', }); const processedCustomFieldsResponse = processAxiosResponse(customFieldsResponse); diff --git a/src/v0/destinations/custify/deleteUsers.js b/src/v0/destinations/custify/deleteUsers.js index 147fcc602c..690768a170 100644 --- a/src/v0/destinations/custify/deleteUsers.js +++ b/src/v0/destinations/custify/deleteUsers.js @@ -17,36 +17,44 @@ const userDeletionHandler = async (userAttributes, config) => { } const { apiKey } = config; - const { userId } = userAttributes; if (!apiKey) { throw new ConfigurationError('api key for deletion not present', 400); } - if (!userId) { - throw new InstrumentationError('User id for deletion not present', 400); - } - const requestUrl = `https://api.custify.com/people?user_id=${userId}`; - const requestOptions = { - headers: { - Authorization: `Bearer ${apiKey}`, - }, - }; + await Promise.all( + userAttributes.map(async (userAttr) => { + const { userId } = userAttr; + if (!userId) { + throw new InstrumentationError('User id for deletion not present', 400); + } + // Reference: https://docs.custify.com/#tag/People/paths/~1people/delete + const requestUrl = `https://api.custify.com/people?user_id=${userId}`; + const requestOptions = { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }; - const deletionResponse = await httpDELETE(requestUrl, requestOptions, { - destType: 'custify', - feature: 'deleteUsers', - }); - const processedDeletionRequest = processAxiosResponse(deletionResponse); - if (processedDeletionRequest.status !== 200 && processedDeletionRequest.status !== 404) { - throw new NetworkError( - JSON.stringify(processedDeletionRequest.response) || 'Error while deleting user', - processedDeletionRequest.status, - { - [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedDeletionRequest.status), - }, - deletionResponse, - ); - } + const deletionResponse = await httpDELETE(requestUrl, requestOptions, { + destType: 'custify', + feature: 'deleteUsers', + requestMethod: 'DELETE', + endpointPath: '/people', + module: 'deletion', + }); + const processedDeletionRequest = processAxiosResponse(deletionResponse); + if (processedDeletionRequest.status !== 200 && processedDeletionRequest.status !== 404) { + throw new NetworkError( + JSON.stringify(processedDeletionRequest.response) || 'Error while deleting user', + processedDeletionRequest.status, + { + [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedDeletionRequest.status), + }, + deletionResponse, + ); + } + }), + ); return { statusCode: 200, status: 'successful' }; }; diff --git a/src/v0/destinations/custify/util.js b/src/v0/destinations/custify/util.js index 8ecabccd2e..b6f3446503 100644 --- a/src/v0/destinations/custify/util.js +++ b/src/v0/destinations/custify/util.js @@ -41,6 +41,8 @@ const createUpdateCompany = async (companyPayload, Config) => { destType: 'custify', feature: 'transformation', endpointPath: `/company`, + requestMethod: 'POST', + module: 'router', }, ); const processedCompanyResponse = processAxiosResponse(companyResponse); diff --git a/src/v0/destinations/customerio/transform.js b/src/v0/destinations/customerio/transform.js index be4486717c..6f2e053001 100644 --- a/src/v0/destinations/customerio/transform.js +++ b/src/v0/destinations/customerio/transform.js @@ -5,7 +5,6 @@ const { InstrumentationError } = require('@rudderstack/integrations-lib'); const { EventType, MappedToDestinationKey } = require('../../../constants'); const { - getErrorRespEvents, getSuccessRespEvents, defaultRequestConfig, addExternalIdToTraits, @@ -174,10 +173,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } let batchResponseList = []; const batchErrorRespList = []; const successRespList = []; diff --git a/src/v0/destinations/delighted/util.js b/src/v0/destinations/delighted/util.js index 2c92685fd7..c690bf5f5c 100644 --- a/src/v0/destinations/delighted/util.js +++ b/src/v0/destinations/delighted/util.js @@ -61,7 +61,13 @@ const userValidity = async (channel, Config, userId) => { }, params: paramsdata, }, - { destType: 'delighted', feature: 'transformation' }, + { + destType: 'delighted', + feature: 'transformation', + requestMethod: 'GET', + endpointPath: '/people.json', + module: 'router', + }, ); if (response && response.data && response.status === 200 && Array.isArray(response.data)) { return response.data.length > 0; diff --git a/src/v0/destinations/drip/util.js b/src/v0/destinations/drip/util.js index a502cf0d20..b7015c9351 100644 --- a/src/v0/destinations/drip/util.js +++ b/src/v0/destinations/drip/util.js @@ -31,7 +31,13 @@ const userExists = async (Config, id) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'drip', feature: 'transformation' }, + { + destType: 'drip', + feature: 'transformation', + requestMethod: 'GET', + endpointPath: '/subscribers/id', + module: 'router', + }, ); if (response && response.status) { return response.status === 200; @@ -70,7 +76,13 @@ const createUpdateUser = async (finalpayload, Config, basicAuth) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'drip', feature: 'transformation' }, + { + destType: 'drip', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/subscribers', + module: 'router', + }, ); if (response) { return response.status === 200 || response.status === 201; diff --git a/src/v0/destinations/engage/deleteUsers.js b/src/v0/destinations/engage/deleteUsers.js index a3c3055c7d..3616d2408d 100644 --- a/src/v0/destinations/engage/deleteUsers.js +++ b/src/v0/destinations/engage/deleteUsers.js @@ -42,6 +42,9 @@ const userDeletionHandler = async (userAttributes, config) => { { destType: 'engage', feature: 'deleteUsers', + requestMethod: 'DELETE', + endpointPath: '/users/userId', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(response); @@ -51,6 +54,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/freshmarketer/utils.js b/src/v0/destinations/freshmarketer/utils.js index 5e3ba6e67e..c80711ff8d 100644 --- a/src/v0/destinations/freshmarketer/utils.js +++ b/src/v0/destinations/freshmarketer/utils.js @@ -50,6 +50,8 @@ const createUpdateAccount = async (payload, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/sales_accounts/upsert`, + requestMethod: 'POST', + module: 'router', }); accountResponse = processAxiosResponse(accountResponse); if (accountResponse.status !== 200 && accountResponse.status !== 201) { @@ -95,6 +97,8 @@ const getUserAccountDetails = async (payload, userEmail, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `crm/sales/api/contacts/upsert?include=sales_accounts`, + requestMethod: 'POST', + module: 'router', }); userSalesAccountResponse = processAxiosResponse(userSalesAccountResponse); if (userSalesAccountResponse.status !== 200 && userSalesAccountResponse.status !== 201) { @@ -145,6 +149,8 @@ const createOrUpdateListDetails = async (listName, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/lists`, + requestMethod: 'GET', + module: 'router', }); listResponse = processAxiosResponse(listResponse); if (listResponse.status !== 200) { @@ -165,6 +171,8 @@ const createOrUpdateListDetails = async (listName, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/lists`, + requestMethod: 'POST', + module: 'router', }); listResponse = processAxiosResponse(listResponse); if (listResponse.status !== 200) { @@ -240,6 +248,8 @@ const getContactsDetails = async (userEmail, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/contacts/upsert`, + requestMethod: 'POST', + module: 'router', }); userResponse = processAxiosResponse(userResponse); if (userResponse.status !== 200 && userResponse.status !== 201) { @@ -314,6 +324,8 @@ const UpdateContactWithLifeCycleStage = async (message, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/selector/lifecycle_stages`, + requestMethod: 'GET', + module: 'router', }); lifeCycleStagesResponse = processAxiosResponse(lifeCycleStagesResponse); if (lifeCycleStagesResponse.status !== 200) { @@ -400,6 +412,8 @@ const UpdateContactWithSalesActivity = async (payload, message, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/selector/sales_activity_types`, + requestMethod: 'GET', + module: 'router', }); salesActivityResponse = processAxiosResponse(salesActivityResponse); if (salesActivityResponse.status !== 200) { diff --git a/src/v0/destinations/freshsales/utils.js b/src/v0/destinations/freshsales/utils.js index 5008fedc2d..977bde0abb 100644 --- a/src/v0/destinations/freshsales/utils.js +++ b/src/v0/destinations/freshsales/utils.js @@ -48,6 +48,8 @@ const createUpdateAccount = async (payload, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/sales_accounts/upsert`, + requestMethod: 'POST', + module: 'router', }); accountResponse = processAxiosResponse(accountResponse); if (accountResponse.status !== 200 && accountResponse.status !== 201) { @@ -92,6 +94,8 @@ const getUserAccountDetails = async (payload, userEmail, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/contacts/upsert?include=sales_accounts`, + requestMethod: 'POST', + module: 'router', }); userSalesAccountResponse = processAxiosResponse(userSalesAccountResponse); if (userSalesAccountResponse.status !== 200 && userSalesAccountResponse.status !== 201) { @@ -148,6 +152,8 @@ const getContactsDetails = async (userEmail, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/contacts/upsert`, + requestMethod: 'POST', + module: 'router', }); userResponse = processAxiosResponse(userResponse); if (userResponse.status !== 200 && userResponse.status !== 201) { @@ -239,6 +245,8 @@ const UpdateContactWithSalesActivity = async (payload, message, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/sales_activity_types`, + requestMethod: 'GET', + module: 'router', }); salesActivityResponse = processAxiosResponse(salesActivityResponse); if (salesActivityResponse.status !== 200) { @@ -319,6 +327,8 @@ const UpdateContactWithLifeCycleStage = async (message, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/lifecycle_stages`, + requestMethod: 'GET', + module: 'router', }); lifeCycleStagesResponse = processAxiosResponse(lifeCycleStagesResponse); if (lifeCycleStagesResponse.status !== 200) { diff --git a/src/v0/destinations/ga/deleteUsers.js b/src/v0/destinations/ga/deleteUsers.js index 06e674048a..524e2c14b4 100644 --- a/src/v0/destinations/ga/deleteUsers.js +++ b/src/v0/destinations/ga/deleteUsers.js @@ -81,6 +81,8 @@ const userDeletionHandler = async (userAttributes, config, rudderDestInfo) => { destType: 'ga', feature: 'deleteUsers', endpointPath: '/userDeletion/userDeletionRequests:upsert', + requestMethod: 'POST', + module: 'deletion', }, ); // process the response to know about refreshing scenario diff --git a/src/v0/destinations/ga4/networkHandler.js b/src/v0/destinations/ga4/networkHandler.js index e4ca1effa8..da8ae2ea30 100644 --- a/src/v0/destinations/ga4/networkHandler.js +++ b/src/v0/destinations/ga4/networkHandler.js @@ -31,9 +31,9 @@ const responseHandler = (responseParams) => { const { description, validationCode, fieldPath } = response.validationMessages[0]; throw new NetworkError( `Validation Server Response Handler:: Validation Error for ${destType} of field path :${fieldPath} | ${validationCode}-${description}`, - status, + 400, { - [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), + [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(400), }, response?.validationMessages[0]?.description, ); diff --git a/src/v0/destinations/gainsight/util.js b/src/v0/destinations/gainsight/util.js index 39e666c1a5..4c7fd58193 100644 --- a/src/v0/destinations/gainsight/util.js +++ b/src/v0/destinations/gainsight/util.js @@ -22,7 +22,13 @@ const searchGroup = async (groupName, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight', feature: 'transformation' }, + { + destType: 'gainsight', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/data/objects/query/Company', + module: 'router', + }, ); } catch (error) { let errMessage = ''; @@ -56,7 +62,13 @@ const createGroup = async (payload, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight', feature: 'transformation' }, + { + destType: 'gainsight', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/data/objects/Company', + module: 'router', + }, ); } catch (error) { let errMessage = ''; @@ -93,7 +105,13 @@ const updateGroup = async (payload, Config) => { keys: 'Name', }, }, - { destType: 'gainsight', feature: 'transformation' }, + { + destType: 'gainsight', + feature: 'transformation', + requestMethod: 'PUT', + endpointPath: '/data/objects/Company', + module: 'router', + }, ); } catch (error) { let errMessage = ''; diff --git a/src/v0/destinations/gainsight_px/util.js b/src/v0/destinations/gainsight_px/util.js index 5109286b3f..e03fbbf148 100644 --- a/src/v0/destinations/gainsight_px/util.js +++ b/src/v0/destinations/gainsight_px/util.js @@ -56,7 +56,13 @@ const objectExists = async (id, Config, objectType) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight_px', feature: 'transformation' }, + { + destType: 'gainsight_px', + feature: 'transformation', + requestMethod: 'GET', + endpointPath: '/accounts/accountId', + module: 'router', + }, ); if (response && response.status === 200) { return { success: true, err: null }; @@ -88,7 +94,13 @@ const createAccount = async (payload, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight_px', feature: 'transformation' }, + { + destType: 'gainsight_px', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/accounts', + module: 'router', + }, ); if (response && response.status === 201) { return { success: true, err: null }; @@ -121,7 +133,13 @@ const updateAccount = async (accountId, payload, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight_px', feature: 'transformation' }, + { + destType: 'gainsight_px', + feature: 'transformation', + requestMethod: 'PUT', + endpointPath: '/accounts/accountId', + module: 'router', + }, ); if (response && response.status === 204) { return { success: true, err: null }; diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js index b4590fb71c..3ea985e773 100644 --- a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js @@ -45,6 +45,8 @@ const getConversionActionId = async (method, headers, params) => { destType: 'google_adwords_enhanced_conversions', feature: 'proxy', endpointPath: `/googleAds:searchStream`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); if (!isHttpStatusSuccess(gaecConversionActionIdResponse.status)) { @@ -98,6 +100,8 @@ const ProxyRequest = async (request) => { destType: 'google_adwords_enhanced_conversions', feature: 'proxy', endpointPath: `/googleAds:uploadOfflineUserData`, + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json b/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json index aeecc3e01b..9c88e59ddb 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json +++ b/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json @@ -1,10 +1,7 @@ [ { "destKey": "operations.create.transaction_attribute.store_attribute.store_code", - "sourceKeys": [ - "properties.store_code", - "properties.storeCode" - ], + "sourceKeys": ["properties.store_code", "properties.storeCode"], "required": false, "metadata": { "type": "toString" @@ -25,10 +22,7 @@ }, { "destKey": "operations.create.transaction_attribute.order_id", - "sourceKeys": [ - "properties.order_id", - "properties.orderId" - ], + "sourceKeys": ["properties.order_id", "properties.orderId"], "required": false, "metadata": { "type": "toString" @@ -52,4 +46,4 @@ ], "required": true } -] \ No newline at end of file +] diff --git a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js index 318b7802df..5541fd6e1e 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js @@ -34,6 +34,8 @@ const createJob = async (endpoint, headers, payload) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/create`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); createJobResponse = processAxiosResponse(createJobResponse); @@ -59,6 +61,8 @@ const addConversionToJob = async (endpoint, headers, jobId, payload) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/addOperations`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); addConversionToJobResponse = processAxiosResponse(addConversionToJobResponse); @@ -83,6 +87,8 @@ const runTheJob = async (endpoint, headers, payload, jobId) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/run`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); return executeJobResponse; @@ -110,6 +116,8 @@ const getConversionCustomVariable = async (headers, params) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/searchStream`, + requestMethod: 'POST', + module: 'dataDelivery', }); searchStreamResponse = processAxiosResponse(searchStreamResponse); if (!isHttpStatusSuccess(searchStreamResponse.status)) { @@ -247,6 +255,8 @@ const ProxyRequest = async (request) => { feature: 'proxy', destType: 'gogole_adwords_offline_conversions', endpointPath: `/proxy`, + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 397895c603..68d4d01fa7 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -9,7 +9,7 @@ const { handleRtTfSingleEventError, defaultBatchRequestConfig, getSuccessRespEvents, - checkInvalidRtTfEvents, + combineBatchRequestsWithSameJobIds, } = require('../../util'); const { CALL_CONVERSION, @@ -185,11 +185,6 @@ const batchEvents = (storeSalesEvents) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const storeSalesEvents = []; // list containing store sales events in batched format const clickCallEvents = []; // list containing click and call events in batched format const errorRespList = []; @@ -229,7 +224,7 @@ const processRouterDest = async (inputs, reqMetadata) => { .concat(storeSalesEventsBatchedResponseList) .concat(clickCallEvents) .concat(errorRespList); - return batchedResponseList; + return combineBatchRequestsWithSameJobIds(batchedResponseList); }; module.exports = { process, processRouterDest }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 599a163c54..ee677373a3 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -64,6 +64,8 @@ const getConversionActionId = async (headers, params) => { destType: 'google_adwords_offline_conversions', feature: 'transformation', endpointPath: `/googleAds:searchStream`, + requestMethod: 'POST', + module: 'dataDelivery', }); searchStreamResponse = processAxiosResponse(searchStreamResponse); if (!isHttpStatusSuccess(searchStreamResponse.status)) { diff --git a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js index dbd055f1a1..3045c1713f 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js @@ -41,6 +41,8 @@ const createJob = async (endpoint, headers, method, params) => { destType: 'google_adwords_remarketing_lists', feature: 'proxy', endpointPath: '/customers/create', + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; @@ -65,6 +67,8 @@ const addUserToJob = async (endpoint, headers, method, jobId, body) => { destType: 'google_adwords_remarketing_lists', feature: 'proxy', endpointPath: '/addOperations', + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; @@ -87,6 +91,8 @@ const runTheJob = async (endpoint, headers, method, jobId) => { destType: 'google_adwords_remarketing_lists', feature: 'proxy', endpointPath: '/run', + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/google_cloud_function/transform.js b/src/v0/destinations/google_cloud_function/transform.js index b218615b44..5e870b9581 100644 --- a/src/v0/destinations/google_cloud_function/transform.js +++ b/src/v0/destinations/google_cloud_function/transform.js @@ -1,9 +1,5 @@ const lodash = require('lodash'); -const { - getSuccessRespEvents, - checkInvalidRtTfEvents, - handleRtTfSingleEventError, -} = require('../../util'); +const { getSuccessRespEvents, handleRtTfSingleEventError } = require('../../util'); const { generateBatchedPayload, validateDestinationConfig } = require('./util'); @@ -40,11 +36,6 @@ function batchEvents(successRespList, maxBatchSize = 10) { // Router transform with batching by default const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const successResponseList = []; const errorRespList = []; const { destination } = inputs[0]; diff --git a/src/v0/destinations/googlesheets/transform.js b/src/v0/destinations/googlesheets/transform.js index 6e27f6192c..79dcf1bdf2 100644 --- a/src/v0/destinations/googlesheets/transform.js +++ b/src/v0/destinations/googlesheets/transform.js @@ -5,7 +5,6 @@ const { getValueFromMessage, getSuccessRespEvents, handleRtTfSingleEventError, - checkInvalidRtTfEvents, } = require('../../util'); const SOURCE_KEYS = ['properties', 'traits', 'context.traits']; @@ -111,10 +110,6 @@ const process = (event) => { const processRouterDest = async (inputs, reqMetadata) => { const successRespList = []; const errorRespList = []; - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } await Promise.all( inputs.map(async (input) => { try { diff --git a/src/v0/destinations/hs/transform.js b/src/v0/destinations/hs/transform.js index c26e024a6c..9eed244af4 100644 --- a/src/v0/destinations/hs/transform.js +++ b/src/v0/destinations/hs/transform.js @@ -1,11 +1,7 @@ const get = require('get-value'); const { InstrumentationError } = require('@rudderstack/integrations-lib'); const { EventType } = require('../../../constants'); -const { - checkInvalidRtTfEvents, - handleRtTfSingleEventError, - getDestinationExternalIDInfoForRetl, -} = require('../../util'); +const { handleRtTfSingleEventError, getDestinationExternalIDInfoForRetl } = require('../../util'); const { API_VERSION } = require('./config'); const { processLegacyIdentify, @@ -71,10 +67,6 @@ const process = async (event) => { // we are batching by default at routerTransform const processRouterDest = async (inputs, reqMetadata) => { let tempInputs = inputs; - const errorRespEvents = checkInvalidRtTfEvents(tempInputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const successRespList = []; const errorRespList = []; diff --git a/src/v0/destinations/hs/util.js b/src/v0/destinations/hs/util.js index e905ee63c4..32ee923f5f 100644 --- a/src/v0/destinations/hs/util.js +++ b/src/v0/destinations/hs/util.js @@ -104,6 +104,8 @@ const getProperties = async (destination) => { destType: 'hs', feature: 'transformation', endpointPath: `/properties/v1/contacts/properties`, + requestMethod: 'GET', + module: 'router', }); hubspotPropertyMapResponse = processAxiosResponse(hubspotPropertyMapResponse); } else { @@ -116,6 +118,8 @@ const getProperties = async (destination) => { destType: 'hs', feature: 'transformation', endpointPath: `/properties/v1/contacts/properties?hapikey`, + requestMethod: 'GET', + module: 'router', }, ); hubspotPropertyMapResponse = processAxiosResponse(hubspotPropertyMapResponse); @@ -365,6 +369,8 @@ const searchContacts = async (message, destination) => { destType: 'hs', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }, ); searchContactsResponse = processAxiosResponse(searchContactsResponse); @@ -375,6 +381,8 @@ const searchContacts = async (message, destination) => { destType: 'hs', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }); searchContactsResponse = processAxiosResponse(searchContactsResponse); } @@ -539,6 +547,8 @@ const performHubSpotSearch = async ( destType: 'hs', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }); const processedResponse = processAxiosResponse(searchResponse); diff --git a/src/v0/destinations/intercom/deleteUsers.js b/src/v0/destinations/intercom/deleteUsers.js index b91f520ade..2c35f29e53 100644 --- a/src/v0/destinations/intercom/deleteUsers.js +++ b/src/v0/destinations/intercom/deleteUsers.js @@ -39,6 +39,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'intercom', feature: 'deleteUsers', endpointPath: '/user_delete_requests', + requestMethod: 'POST', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) { @@ -47,6 +49,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/iterable/deleteUsers.js b/src/v0/destinations/iterable/deleteUsers.js index a179a8930f..015a9de9a0 100644 --- a/src/v0/destinations/iterable/deleteUsers.js +++ b/src/v0/destinations/iterable/deleteUsers.js @@ -36,6 +36,9 @@ const userDeletionHandler = async (userAttributes, config) => { const resp = await httpDELETE(url, requestOptions, { destType: 'iterable', feature: 'deleteUsers', + endpointPath: '/users/byUserId/uId', + requestMethod: 'DELETE', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) { @@ -46,6 +49,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/iterable/transform.js b/src/v0/destinations/iterable/transform.js index 64bdcfcfa4..207a8d1186 100644 --- a/src/v0/destinations/iterable/transform.js +++ b/src/v0/destinations/iterable/transform.js @@ -18,7 +18,6 @@ const { const { constructPayload, defaultRequestConfig, - checkInvalidRtTfEvents, defaultPostRequestConfig, handleRtTfSingleEventError, removeUndefinedAndNullValues, @@ -162,11 +161,6 @@ const process = (event) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const batchedEvents = batchEvents(inputs); const response = await Promise.all( batchedEvents.map(async (listOfEvents) => { diff --git a/src/v0/destinations/kafka/transform.js b/src/v0/destinations/kafka/transform.js index b08c717475..78f278575a 100644 --- a/src/v0/destinations/kafka/transform.js +++ b/src/v0/destinations/kafka/transform.js @@ -6,7 +6,6 @@ const { getHashFromArray, removeUndefinedAndNullValues, getSuccessRespEvents, - getErrorRespEvents, } = require('../../util'); const filterConfigTopics = (message, destination) => { @@ -38,10 +37,6 @@ const filterConfigTopics = (message, destination) => { const batch = (destEvents) => { const respList = []; - if (!Array.isArray(destEvents) || destEvents.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } // Grouping the events by topic const groupedEvents = groupBy(destEvents, (event) => event.message.topic); diff --git a/src/v0/destinations/klaviyo/transform.js b/src/v0/destinations/klaviyo/transform.js index 3c2f8137f2..a0fe3e81a7 100644 --- a/src/v0/destinations/klaviyo/transform.js +++ b/src/v0/destinations/klaviyo/transform.js @@ -32,7 +32,6 @@ const { addExternalIdToTraits, adduserIdFromExternalId, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, flattenJson, isNewStatusCodesAccepted, @@ -320,10 +319,6 @@ const getEventChunks = (event, subscribeRespList, nonSubscribeRespList) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let batchResponseList = []; const batchErrorRespList = []; const subscribeRespList = []; diff --git a/src/v0/destinations/klaviyo/util.js b/src/v0/destinations/klaviyo/util.js index 60b334f3a2..df2dbb4712 100644 --- a/src/v0/destinations/klaviyo/util.js +++ b/src/v0/destinations/klaviyo/util.js @@ -45,6 +45,8 @@ const getIdFromNewOrExistingProfile = async (endpoint, payload, requestOptions) destType: 'klaviyo', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }, ); diff --git a/src/v0/destinations/kustomer/util.js b/src/v0/destinations/kustomer/util.js index 571a03f139..530983bb26 100644 --- a/src/v0/destinations/kustomer/util.js +++ b/src/v0/destinations/kustomer/util.js @@ -139,7 +139,13 @@ const fetchKustomer = async (url, destination) => { Authorization: `Bearer ${destination.Config.apiKey}`, }, }, - { destType: 'kustomer', feature: 'transformation' }, + { + destType: 'kustomer', + feature: 'transformation', + endpointPath: '/customers/email', + requestMethod: 'GET', + module: 'processor', + }, ); } catch (err) { if (err.response) { diff --git a/src/v0/destinations/lambda/transform.js b/src/v0/destinations/lambda/transform.js index 1570a69ec3..efc68b89d6 100644 --- a/src/v0/destinations/lambda/transform.js +++ b/src/v0/destinations/lambda/transform.js @@ -1,5 +1,6 @@ const _ = require('lodash'); -const { getErrorRespEvents, getSuccessRespEvents } = require('../../util'); +const { getErrorRespEvents } = require('@rudderstack/integrations-lib'); +const { getSuccessRespEvents } = require('../../util'); const { ConfigurationError } = require('@rudderstack/integrations-lib'); const DEFAULT_INVOCATION_TYPE = 'Event'; // asynchronous invocation diff --git a/src/v0/destinations/mailchimp/transform.js b/src/v0/destinations/mailchimp/transform.js index 894f70672a..87f547c124 100644 --- a/src/v0/destinations/mailchimp/transform.js +++ b/src/v0/destinations/mailchimp/transform.js @@ -3,7 +3,6 @@ const { InstrumentationError, ConfigurationError } = require('@rudderstack/integ const { defaultPutRequestConfig, handleRtTfSingleEventError, - checkInvalidRtTfEvents, constructPayload, defaultPostRequestConfig, isDefinedAndNotNull, @@ -162,10 +161,6 @@ const getEventChunks = (event, identifyRespList, trackRespList) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let batchResponseList = []; const batchErrorRespList = []; const identifyRespList = []; diff --git a/src/v0/destinations/mailchimp/utils.js b/src/v0/destinations/mailchimp/utils.js index e1e2e9883b..1f4fc03ee5 100644 --- a/src/v0/destinations/mailchimp/utils.js +++ b/src/v0/destinations/mailchimp/utils.js @@ -163,7 +163,13 @@ const checkIfMailExists = async (apiKey, datacenterId, audienceId, email) => { Authorization: `Basic ${basicAuth}`, }, }, - { destType: 'mailchimp', feature: 'transformation' }, + { + destType: 'mailchimp', + feature: 'transformation', + endpointPath: '/lists/audienceId/members/email', + requestMethod: 'GET', + module: 'router', + }, ); if (response?.data?.contact_id) { userStatus.exists = true; @@ -194,7 +200,13 @@ const checkIfDoubleOptIn = async (apiKey, datacenterId, audienceId) => { Authorization: `Basic ${basicAuth}`, }, }, - { destType: 'mailchimp', feature: 'transformation' }, + { + destType: 'mailchimp', + feature: 'transformation', + endpointPath: '/lists/audienceId', + requestMethod: 'GET', + module: 'router', + }, ); } catch (error) { const status = error.status || 400; diff --git a/src/v0/destinations/mailjet/transform.js b/src/v0/destinations/mailjet/transform.js index 9156bf45e9..78b4f766d1 100644 --- a/src/v0/destinations/mailjet/transform.js +++ b/src/v0/destinations/mailjet/transform.js @@ -1,7 +1,6 @@ const lodash = require('lodash'); const { TransformationError, InstrumentationError } = require('@rudderstack/integrations-lib'); const { - getErrorRespEvents, getSuccessRespEvents, defaultRequestConfig, defaultPostRequestConfig, @@ -121,10 +120,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } let batchResponseList = []; const batchErrorRespList = []; const successRespList = []; diff --git a/src/v0/destinations/mailmodo/transform.js b/src/v0/destinations/mailmodo/transform.js index 756522939d..a61ca8a73d 100644 --- a/src/v0/destinations/mailmodo/transform.js +++ b/src/v0/destinations/mailmodo/transform.js @@ -10,7 +10,6 @@ const { defaultPostRequestConfig, defaultBatchRequestConfig, removeUndefinedAndNullValues, - getErrorRespEvents, getSuccessRespEvents, handleRtTfSingleEventError, } = require('../../util'); @@ -191,11 +190,6 @@ function getEventChunks(event, identifyEventChunks, eventResponseList) { } const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } - const identifyEventChunks = []; // list containing identify events in batched format const eventResponseList = []; // list containing other events in batched format const errorRespList = []; diff --git a/src/v0/destinations/marketo/networkHandler.js b/src/v0/destinations/marketo/networkHandler.js index 1d4b316e8d..ac555accfe 100644 --- a/src/v0/destinations/marketo/networkHandler.js +++ b/src/v0/destinations/marketo/networkHandler.js @@ -4,7 +4,7 @@ const { proxyRequest, prepareProxyRequest } = require('../../../adapters/network const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const responseHandler = (responseParams) => { - const { destinationResponse, destType,rudderJobMetadata } = responseParams; + const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; const { status } = destinationResponse; const authCache = v0Utils.getDestAuthCacheInstance(destType); diff --git a/src/v0/destinations/marketo/transform.js b/src/v0/destinations/marketo/transform.js index 5000ef506b..b811596f95 100644 --- a/src/v0/destinations/marketo/transform.js +++ b/src/v0/destinations/marketo/transform.js @@ -7,6 +7,7 @@ const { InstrumentationError, ConfigurationError, UnauthorizedError, + getErrorRespEvents, } = require('@rudderstack/integrations-lib'); const stats = require('../../../util/stats'); const { EventType, MappedToDestinationKey } = require('../../../constants'); @@ -28,10 +29,8 @@ const { getFieldValueFromMessage, getDestinationExternalID, getSuccessRespEvents, - getErrorRespEvents, isDefinedAndNotNull, generateErrorObject, - checkInvalidRtTfEvents, handleRtTfSingleEventError, } = require('../../util'); const Cache = require('../../util/cache'); @@ -456,10 +455,6 @@ const process = async (event) => { const processRouterDest = async (inputs, reqMetadata) => { // Token needs to be generated for marketo which will be done on input level. // If destination information is not present Error should be thrown - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let token; try { token = await getAuthToken(formatConfig(inputs[0].destination)); diff --git a/src/v0/destinations/marketo/util.js b/src/v0/destinations/marketo/util.js index 54ff70708a..b3a24fb411 100644 --- a/src/v0/destinations/marketo/util.js +++ b/src/v0/destinations/marketo/util.js @@ -248,6 +248,8 @@ const sendGetRequest = async (url, options) => { destType: 'marketo', feature: 'transformation', endpointPath: `/v1/leads`, + requestMethod: 'GET', + module: 'router', }); const processedResponse = processAxiosResponse(clientResponse); return processedResponse; @@ -264,6 +266,8 @@ const sendPostRequest = async (url, data, options) => { destType: 'marketo', feature: 'transformation', endpointPath: `/v1/leads`, + requestMethod: 'POST', + module: 'router', }); const processedResponse = processAxiosResponse(clientResponse); return processedResponse; diff --git a/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js b/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js index e6f5662000..db3b13eeb8 100644 --- a/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js +++ b/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js @@ -33,6 +33,9 @@ const getJobsStatus = async (event, type, accessToken) => { const { processedResponse: resp } = await handleHttpRequest('get', url, requestOptions, { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads/batch/', + requestMethod: 'GET', + module: 'router', }); const endTime = Date.now(); const requestTime = endTime - startTime; diff --git a/src/v0/destinations/marketo_bulk_upload/fileUpload.js b/src/v0/destinations/marketo_bulk_upload/fileUpload.js index 9c42fdc98d..b49a265fd5 100644 --- a/src/v0/destinations/marketo_bulk_upload/fileUpload.js +++ b/src/v0/destinations/marketo_bulk_upload/fileUpload.js @@ -198,6 +198,9 @@ const getImportID = async (input, config, accessToken, csvHeader) => { { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads.json', + requestMethod: 'POST', + module: 'router', }, ); const endTime = Date.now(); diff --git a/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js b/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js index 875b0d8280..aa4b3aacc4 100644 --- a/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js +++ b/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js @@ -296,6 +296,10 @@ describe('getAccessToken', () => { expect(handleHttpRequest).toHaveBeenCalledWith('get', url, { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/identity/oauth/token', + feature: 'transformation', + module: 'router', + requestMethod: 'GET', }); }); diff --git a/src/v0/destinations/marketo_bulk_upload/poll.js b/src/v0/destinations/marketo_bulk_upload/poll.js index db7a634774..f53347d6e5 100644 --- a/src/v0/destinations/marketo_bulk_upload/poll.js +++ b/src/v0/destinations/marketo_bulk_upload/poll.js @@ -26,6 +26,9 @@ const getPollStatus = async (event) => { { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads/batch/importId.json', + requestMethod: 'GET', + module: 'router', }, ); if (!isHttpStatusSuccess(pollStatus.status)) { diff --git a/src/v0/destinations/marketo_bulk_upload/util.js b/src/v0/destinations/marketo_bulk_upload/util.js index fac04af431..4c99ba7483 100644 --- a/src/v0/destinations/marketo_bulk_upload/util.js +++ b/src/v0/destinations/marketo_bulk_upload/util.js @@ -128,6 +128,9 @@ const getAccessToken = async (config) => { const { processedResponse: accessTokenResponse } = await handleHttpRequest('get', url, { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/identity/oauth/token', + requestMethod: 'GET', + module: 'router', }); // sample response : {response: '[ENOTFOUND] :: DNS lookup failed', status: 400} @@ -352,6 +355,9 @@ const getFieldSchemaMap = async (accessToken, munchkinId) => { { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads/describe2.json', + requestMethod: 'GET', + module: 'router', }, ); diff --git a/src/v0/destinations/marketo_static_list/networkHandler.js b/src/v0/destinations/marketo_static_list/networkHandler.js index 9e73cd1f91..086378cf6a 100644 --- a/src/v0/destinations/marketo_static_list/networkHandler.js +++ b/src/v0/destinations/marketo_static_list/networkHandler.js @@ -7,7 +7,7 @@ const { DESTINATION } = require('./config'); const responseHandler = (responseParams) => { const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; - const { status} = 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/transform.js b/src/v0/destinations/marketo_static_list/transform.js index 294e34f91b..92c137c614 100644 --- a/src/v0/destinations/marketo_static_list/transform.js +++ b/src/v0/destinations/marketo_static_list/transform.js @@ -1,6 +1,10 @@ const lodash = require('lodash'); const cloneDeep = require('lodash/cloneDeep'); -const { InstrumentationError, UnauthorizedError } = require('@rudderstack/integrations-lib'); +const { + InstrumentationError, + UnauthorizedError, + getErrorRespEvents, +} = require('@rudderstack/integrations-lib'); const { defaultPostRequestConfig, defaultDeleteRequestConfig, @@ -9,11 +13,7 @@ const { } = require('../../util'); const { AUTH_CACHE_TTL, JSON_MIME_TYPE } = require('../../util/constant'); const { getIds, validateMessageType } = require('./util'); -const { - getDestinationExternalID, - defaultRequestConfig, - getErrorRespEvents, -} = require('../../util'); +const { getDestinationExternalID, defaultRequestConfig } = require('../../util'); const { formatConfig, MAX_LEAD_IDS_SIZE } = require('./config'); const Cache = require('../../util/cache'); const { getAuthToken } = require('../marketo/transform'); diff --git a/src/v0/destinations/marketo_static_list/transformV2.js b/src/v0/destinations/marketo_static_list/transformV2.js index 912d548d09..73d4bec8f8 100644 --- a/src/v0/destinations/marketo_static_list/transformV2.js +++ b/src/v0/destinations/marketo_static_list/transformV2.js @@ -1,5 +1,9 @@ const lodash = require('lodash'); -const { InstrumentationError, UnauthorizedError } = require('@rudderstack/integrations-lib'); +const { + InstrumentationError, + UnauthorizedError, + getErrorRespEvents, +} = require('@rudderstack/integrations-lib'); const { defaultPostRequestConfig, defaultDeleteRequestConfig, @@ -7,7 +11,6 @@ const { getSuccessRespEvents, isDefinedAndNotNull, generateErrorObject, - getErrorRespEvents, } = require('../../util'); const { JSON_MIME_TYPE } = require('../../util/constant'); const { MAX_LEAD_IDS_SIZE } = require('./config'); diff --git a/src/v0/destinations/mautic/utils.js b/src/v0/destinations/mautic/utils.js index d8ad8dbffc..7a1827e769 100644 --- a/src/v0/destinations/mautic/utils.js +++ b/src/v0/destinations/mautic/utils.js @@ -183,6 +183,9 @@ const searchContactIds = async (message, Config, baseUrl) => { { destType: 'mautic', feature: 'transformation', + endpointPath: '/contacts', + requestMethod: 'GET', + module: 'router', }, ); searchContactsResponse = processAxiosResponse(searchContactsResponse); diff --git a/src/v0/destinations/monday/util.js b/src/v0/destinations/monday/util.js index 736f0133fd..872fad42a7 100644 --- a/src/v0/destinations/monday/util.js +++ b/src/v0/destinations/monday/util.js @@ -195,6 +195,8 @@ const getBoardDetails = async (url, boardID, apiToken) => { destType: 'monday', feature: 'transformation', endpointPath: '/v2', + requestMethod: 'POST', + module: 'router', }, ); const boardDetailsResponse = processAxiosResponse(clientResponse); diff --git a/src/v0/destinations/mp/deleteUsers.js b/src/v0/destinations/mp/deleteUsers.js index f01475ef2b..e1240c609d 100644 --- a/src/v0/destinations/mp/deleteUsers.js +++ b/src/v0/destinations/mp/deleteUsers.js @@ -49,6 +49,8 @@ const deleteProfile = async (userAttributes, config) => { destType: 'mp', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }, ); if (!isHttpStatusSuccess(handledDelResponse.status)) { @@ -57,6 +59,7 @@ const deleteProfile = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); @@ -104,6 +107,8 @@ const createDeletionTask = async (userAttributes, config) => { destType: 'mp', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }, ); if (!isHttpStatusSuccess(handledDelResponse.status)) { diff --git a/src/v0/destinations/mp/transform.js b/src/v0/destinations/mp/transform.js index 41a814683d..493169cd4e 100644 --- a/src/v0/destinations/mp/transform.js +++ b/src/v0/destinations/mp/transform.js @@ -14,10 +14,10 @@ const { removeUndefinedValues, toUnixTimestampInMS, getFieldValueFromMessage, - checkInvalidRtTfEvents, handleRtTfSingleEventError, groupEventsByType, parseConfigArray, + combineBatchRequestsWithSameJobIds, } = require('../../util'); const { ConfigCategory, @@ -33,7 +33,6 @@ const { createIdentifyResponse, isImportAuthCredentialsAvailable, buildUtmParams, - combineBatchRequestsWithSameJobIds, groupEventsByEndpoint, batchEvents, trimTraits, @@ -460,11 +459,6 @@ const process = (event) => processSingleMessage(event.message, event.destination // Ref: https://help.mixpanel.com/hc/en-us/articles/115004613766-Default-Properties-Collected-by-Mixpanel // Ref: https://help.mixpanel.com/hc/en-us/articles/115004561786-Track-UTM-Tags const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const groupedEvents = groupEventsByType(inputs); const response = await Promise.all( groupedEvents.map(async (listOfEvents) => { diff --git a/src/v0/destinations/mp/util.js b/src/v0/destinations/mp/util.js index c01d2308b7..8e943f41dd 100644 --- a/src/v0/destinations/mp/util.js +++ b/src/v0/destinations/mp/util.js @@ -139,44 +139,6 @@ const isImportAuthCredentialsAvailable = (destination) => destination.Config.serviceAccountUserName && destination.Config.projectId); -/** - * Finds an existing batch based on metadata JobIds from the provided batch and metadataMap. - * @param {*} batch - * @param {*} metadataMap The map containing metadata items indexed by JobIds. - * @returns - */ -const findExistingBatch = (batch, metadataMap) => { - let existingBatch = null; - - // eslint-disable-next-line no-restricted-syntax - for (const metadataItem of batch.metadata) { - if (metadataMap.has(metadataItem.jobId)) { - existingBatch = metadataMap.get(metadataItem.jobId); - break; - } - } - - return existingBatch; -}; - -/** - * Removes duplicate metadata within each merged batch object. - * @param {*} mergedBatches An array of merged batch objects. - */ -const removeDuplicateMetadata = (mergedBatches) => { - mergedBatches.forEach((batch) => { - const metadataSet = new Set(); - // eslint-disable-next-line no-param-reassign - batch.metadata = batch.metadata.filter((metadataItem) => { - if (!metadataSet.has(metadataItem.jobId)) { - metadataSet.add(metadataItem.jobId); - return true; - } - return false; - }); - }); -}; - /** * Builds UTM parameters from a campaign object. * @@ -273,58 +235,6 @@ const batchEvents = (successRespList, maxBatchSize, reqMetadata) => { }); }; -/** - * Combines batched requests with the same JobIds. - * @param {*} inputBatches The array of batched request objects. - * @returns The combined batched requests with merged JobIds. - * - */ -const combineBatchRequestsWithSameJobIds = (inputBatches) => { - const combineBatches = (batches) => { - const clonedBatches = [...batches]; - const mergedBatches = []; - const metadataMap = new Map(); - - clonedBatches.forEach((batch) => { - const existingBatch = findExistingBatch(batch, metadataMap); - - if (existingBatch) { - // Merge batchedRequests arrays - existingBatch.batchedRequest = [ - ...(Array.isArray(existingBatch.batchedRequest) - ? existingBatch.batchedRequest - : [existingBatch.batchedRequest]), - ...(Array.isArray(batch.batchedRequest) ? batch.batchedRequest : [batch.batchedRequest]), - ]; - - // Merge metadata - batch.metadata.forEach((metadataItem) => { - if (!metadataMap.has(metadataItem.jobId)) { - metadataMap.set(metadataItem.jobId, existingBatch); - } - existingBatch.metadata.push(metadataItem); - }); - } else { - mergedBatches.push(batch); - batch.metadata.forEach((metadataItem) => { - metadataMap.set(metadataItem.jobId, batch); - }); - } - }); - - // Remove duplicate metadata within each merged object - removeDuplicateMetadata(mergedBatches); - - return mergedBatches; - }; - // We need to run this twice because in first pass some batches might not get merged - // and in second pass they might get merged - // Example: [[{jobID:1}, {jobID:2}], [{jobID:3}], [{jobID:1}, {jobID:3}]] - // 1st pass: [[{jobID:1}, {jobID:2}, {jobID:3}], [{jobID:3}]] - // 2nd pass: [[{jobID:1}, {jobID:2}, {jobID:3}]] - return combineBatches(combineBatches(inputBatches)); -}; - /** * Trims the traits and contextTraits objects based on the setOnceProperties array and returns an object containing the modified traits, contextTraits, and setOnce properties. * @@ -398,6 +308,5 @@ module.exports = { groupEventsByEndpoint, generateBatchedPayloadForArray, batchEvents, - combineBatchRequestsWithSameJobIds, trimTraits, }; diff --git a/src/v0/destinations/mp/util.test.js b/src/v0/destinations/mp/util.test.js index ebf140fadd..866119a336 100644 --- a/src/v0/destinations/mp/util.test.js +++ b/src/v0/destinations/mp/util.test.js @@ -1,5 +1,4 @@ const { - combineBatchRequestsWithSameJobIds, groupEventsByEndpoint, batchEvents, generateBatchedPayloadForArray, @@ -263,235 +262,6 @@ describe('Mixpanel utils test', () => { }); }); - describe('Unit test cases for combineBatchRequestsWithSameJobIds', () => { - it('Combine batch request with same jobIds', async () => { - const input = [ - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - metadata: [ - { - jobId: 1, - }, - { - jobId: 4, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/import/', - }, - metadata: [ - { - jobId: 3, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 1, - }, - { - jobId: 3, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/import/', - }, - metadata: [ - { - jobId: 6, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - ]; - - const expectedOutput = [ - { - batchedRequest: [ - { - endpoint: 'https://api.mixpanel.com/track/', - }, - { - endpoint: 'https://api.mixpanel.com/engage/', - }, - { - endpoint: 'https://api.mixpanel.com/import/', - }, - ], - metadata: [ - { - jobId: 1, - }, - { - jobId: 4, - }, - { - jobId: 3, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/import/', - }, - metadata: [ - { - jobId: 6, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - ]; - expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); - }); - - it('Each batchRequest contains unique jobIds (no event multiplexing)', async () => { - const input = [ - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - metadata: [ - { - jobId: 1, - }, - { - jobId: 4, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 2, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - ]; - - const expectedOutput = [ - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/track/', - }, - - metadata: [ - { - jobId: 1, - }, - { - jobId: 4, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 2, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - { - batchedRequest: { - endpoint: 'https://api.mixpanel.com/engage/', - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: true, - statusCode: 200, - destination: destinationMock, - }, - ]; - expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); - }); - }); - describe('Unit test cases for generateBatchedPayloadForArray', () => { it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => { const events = [ diff --git a/src/v0/destinations/ometria/transform.js b/src/v0/destinations/ometria/transform.js index 55038e10b8..5eff77bd15 100644 --- a/src/v0/destinations/ometria/transform.js +++ b/src/v0/destinations/ometria/transform.js @@ -14,7 +14,6 @@ const { getFieldValueFromMessage, getIntegrationsObj, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, } = require('../../util/index'); const { @@ -250,10 +249,6 @@ const process = (event) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const inputChunks = returnArrayOfSubarrays(inputs, MAX_BATCH_SIZE); const successList = []; const errorList = []; diff --git a/src/v0/destinations/pardot/transform.js b/src/v0/destinations/pardot/transform.js index b32b8967bd..3dbe57ecc7 100644 --- a/src/v0/destinations/pardot/transform.js +++ b/src/v0/destinations/pardot/transform.js @@ -44,7 +44,6 @@ const { getFieldValueFromMessage, removeUndefinedValues, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, getAccessToken, } = require('../../util'); @@ -150,11 +149,6 @@ const processEvent = (metadata, message, destination) => { const process = (event) => processEvent(event.metadata, event.message, event.destination); const processRouterDest = (events, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(events); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const responseList = events.map((event) => { try { return getSuccessRespEvents(process(event), [event.metadata], event.destination); diff --git a/src/v0/destinations/pinterest_tag/transform.js b/src/v0/destinations/pinterest_tag/transform.js index ee7e2e5b19..f8ccfd48ea 100644 --- a/src/v0/destinations/pinterest_tag/transform.js +++ b/src/v0/destinations/pinterest_tag/transform.js @@ -5,7 +5,6 @@ const { defaultRequestConfig, defaultPostRequestConfig, getSuccessRespEvents, - getErrorRespEvents, constructPayload, defaultBatchRequestConfig, removeUndefinedAndNullValues, @@ -172,11 +171,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } - const successRespList = []; const batchErrorRespList = []; inputs.forEach((input) => { diff --git a/src/v0/destinations/profitwell/utils.js b/src/v0/destinations/profitwell/utils.js index acc4db2035..1b23561721 100644 --- a/src/v0/destinations/profitwell/utils.js +++ b/src/v0/destinations/profitwell/utils.js @@ -188,6 +188,9 @@ const getSubscriptionHistory = async (endpoint, options) => { const res = await httpGET(endpoint, requestOptions, { destType: 'profitwell', feature: 'transformation', + endpointPath: '/users/userId', + requestMethod: 'GET', + module: 'router', }); return res; }; diff --git a/src/v0/destinations/rakuten/networkHandler.js b/src/v0/destinations/rakuten/networkHandler.js index 6c89d83947..4c97a23e51 100644 --- a/src/v0/destinations/rakuten/networkHandler.js +++ b/src/v0/destinations/rakuten/networkHandler.js @@ -18,7 +18,13 @@ const proxyRequest = async (request, destType) => { headers, method, }; - const response = await httpSend(requestOptions, { feature: 'proxy', destType }); + const response = await httpSend(requestOptions, { + feature: 'proxy', + destType, + endpointPath: '/ep', + requestMethod: 'GET', + module: 'dataDelivery', + }); return response; }; const extractContent = (xmlPayload, tagName) => { diff --git a/src/v0/destinations/salesforce/transform.js b/src/v0/destinations/salesforce/transform.js index 5ada9dfaa0..b8f032c5bf 100644 --- a/src/v0/destinations/salesforce/transform.js +++ b/src/v0/destinations/salesforce/transform.js @@ -3,6 +3,7 @@ const cloneDeep = require('lodash/cloneDeep'); const { InstrumentationError, NetworkInstrumentationError, + getErrorRespEvents, } = require('@rudderstack/integrations-lib'); const { EventType, MappedToDestinationKey } = require('../../../constants'); const { @@ -20,10 +21,8 @@ const { constructPayload, getFirstAndLastName, getSuccessRespEvents, - getErrorRespEvents, addExternalIdToTraits, getDestinationExternalIDObjectForRetl, - checkInvalidRtTfEvents, handleRtTfSingleEventError, generateErrorObject, isHttpStatusSuccess, @@ -120,6 +119,9 @@ async function getSaleforceIdForRecord( { destType: 'salesforce', feature: 'transformation', + endpointPath: '/parameterizedSearch', + requestMethod: 'GET', + module: 'router', }, ); if (!isHttpStatusSuccess(processedsfSearchResponse.status)) { @@ -233,6 +235,9 @@ async function getSalesforceIdFromPayload( { destType: 'salesforce', feature: 'transformation', + endpointPath: '/parameterizedSearch', + requestMethod: 'GET', + module: 'router', }, ); @@ -348,10 +353,6 @@ async function process(event) { } const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let authInfo; try { authInfo = await collectAuthorizationInfo(inputs[0]); diff --git a/src/v0/destinations/salesforce/utils.js b/src/v0/destinations/salesforce/utils.js index 96735ecc17..85061ce2b2 100644 --- a/src/v0/destinations/salesforce/utils.js +++ b/src/v0/destinations/salesforce/utils.js @@ -133,6 +133,9 @@ const getAccessToken = async (destination) => { { destType: 'salesforce', feature: 'transformation', + endpointPath: '/services/oauth2/token', + requestMethod: 'POST', + module: 'router', }, ); // If the request fails, throwing error. diff --git a/src/v0/destinations/sendgrid/deleteUsers.js b/src/v0/destinations/sendgrid/deleteUsers.js index 8410f41296..ccd277d90d 100644 --- a/src/v0/destinations/sendgrid/deleteUsers.js +++ b/src/v0/destinations/sendgrid/deleteUsers.js @@ -85,6 +85,9 @@ const userDeletionHandler = async (userAttributes, config) => { const deletionResponse = await httpDELETE(endpoint, requestOptions, { destType: 'sendgrid', feature: 'deleteUsers', + endpointPath: '/marketing/contacts', + requestMethod: 'DELETE', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(deletionResponse); @@ -94,6 +97,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/sendgrid/transform.js b/src/v0/destinations/sendgrid/transform.js index 5038fedf7b..c32e34c489 100644 --- a/src/v0/destinations/sendgrid/transform.js +++ b/src/v0/destinations/sendgrid/transform.js @@ -9,7 +9,6 @@ const { ErrorMessage, isEmptyObject, constructPayload, - getErrorRespEvents, extractCustomFields, getValueFromMessage, defaultRequestConfig, @@ -236,10 +235,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = async (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } let batchResponseList = []; const batchErrorRespList = []; const successRespList = []; diff --git a/src/v0/destinations/sendgrid/util.js b/src/v0/destinations/sendgrid/util.js index 1df34bfe69..7105c5cda5 100644 --- a/src/v0/destinations/sendgrid/util.js +++ b/src/v0/destinations/sendgrid/util.js @@ -445,6 +445,9 @@ const fetchCustomFields = async (destination) => { const resonse = await httpGET(endpoint, requestOptions, { destType: 'sendgrid', feature: 'transformation', + endpointPath: '/marketing/field_definitions', + requestMethod: 'GET', + module: 'router', }); const processedResponse = processAxiosResponse(resonse); if (isHttpStatusSuccess(processedResponse.status)) { diff --git a/src/v0/destinations/sendinblue/util.js b/src/v0/destinations/sendinblue/util.js index 9ad37fc9b7..9fded8e493 100644 --- a/src/v0/destinations/sendinblue/util.js +++ b/src/v0/destinations/sendinblue/util.js @@ -60,6 +60,9 @@ const checkIfContactExists = async (identifier, apiKey) => { const contactDetailsResponse = await httpGET(endpoint, requestOptions, { destType: 'sendinblue', feature: 'transformation', + endpointPath: '/contacts', + requestMethod: 'GET', + module: 'router', }); const processedContactDetailsResponse = processAxiosResponse(contactDetailsResponse); diff --git a/src/v0/destinations/sfmc/transform.js b/src/v0/destinations/sfmc/transform.js index 7623d751f1..553ceb2828 100644 --- a/src/v0/destinations/sfmc/transform.js +++ b/src/v0/destinations/sfmc/transform.js @@ -44,7 +44,13 @@ const getToken = async (clientId, clientSecret, subdomain) => { { 'Content-Type': JSON_MIME_TYPE, }, - { destType: 'sfmc', feature: 'transformation' }, + { + destType: 'sfmc', + feature: 'transformation', + endpointPath: '/token', + requestMethod: 'POST', + module: 'router', + }, ); if (resp && resp.data) { return resp.data.access_token; diff --git a/src/v0/destinations/snapchat_conversion/transform.js b/src/v0/destinations/snapchat_conversion/transform.js index 37d321a468..6fec6313a4 100644 --- a/src/v0/destinations/snapchat_conversion/transform.js +++ b/src/v0/destinations/snapchat_conversion/transform.js @@ -12,7 +12,6 @@ const { getSuccessRespEvents, isAppleFamily, getValidDynamicFormConfig, - checkInvalidRtTfEvents, handleRtTfSingleEventError, batchMultiplexedEvents, } = require('../../util'); @@ -358,11 +357,6 @@ const process = (event) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const eventsChunk = []; // temporary variable to divide payload into chunks const errorRespList = []; inputs.forEach((event) => { diff --git a/src/v0/destinations/snapchat_custom_audience/networkHandler.js b/src/v0/destinations/snapchat_custom_audience/networkHandler.js index feedaea3e3..6044216293 100644 --- a/src/v0/destinations/snapchat_custom_audience/networkHandler.js +++ b/src/v0/destinations/snapchat_custom_audience/networkHandler.js @@ -43,6 +43,9 @@ const scAudienceProxyRequest = async (request) => { const response = await httpSend(requestOptions, { feature: 'proxy', destType: 'snapchat_custom_audience', + endpointPath: '/segments/segmentId/users', + requestMethod: requestOptions?.method, + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/sprig/deleteUsers.js b/src/v0/destinations/sprig/deleteUsers.js index a886bbbafc..01044adcd1 100644 --- a/src/v0/destinations/sprig/deleteUsers.js +++ b/src/v0/destinations/sprig/deleteUsers.js @@ -49,7 +49,9 @@ const userDeletionHandler = async (userAttributes, config) => { { destType: 'sprig', feature: 'deleteUsers', - endpointPath: 'api.sprig.com/v2/purge/visitors', + endpointPath: '/purge/visitors', + requestMethod: 'POST', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(deletionResponse); @@ -59,6 +61,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/the_trade_desk/networkHandler.js b/src/v0/destinations/the_trade_desk/networkHandler.js index 5d443709b7..aebbfc0785 100644 --- a/src/v0/destinations/the_trade_desk/networkHandler.js +++ b/src/v0/destinations/the_trade_desk/networkHandler.js @@ -8,37 +8,28 @@ const { getSignatureHeader } = require('../../../cdk/v2/destinations/the_trade_d const { isHttpStatusSuccess } = require('../../util/index'); const tags = require('../../util/tags'); const { JSON_MIME_TYPE } = require('../../util/constant'); -const { - REAL_TIME_CONVERSION_ENDPOINT, -} = require('../../../cdk/v2/destinations/the_trade_desk/config'); const proxyRequest = async (request) => { const { endpoint, data, method, params, headers, config } = prepareProxyRequest(request); - let ProxyHeaders = { - ...headers, - 'Content-Type': JSON_MIME_TYPE, - }; - - // For first party data flow - if (endpoint !== REAL_TIME_CONVERSION_ENDPOINT) { - if (!config?.advertiserSecretKey) { - throw new PlatformError('Advertiser secret key is missing in destination config. Aborting'); - } - if (!process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY) { - throw new PlatformError('Data provider secret key is missing. Aborting'); - } + if (!config?.advertiserSecretKey) { + throw new PlatformError('Advertiser secret key is missing in destination config. Aborting'); + } - ProxyHeaders = { - ...ProxyHeaders, - TtdSignature: getSignatureHeader(data, config.advertiserSecretKey), - 'TtdSignature-dp': getSignatureHeader( - data, - process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY, - ), - }; + if (!process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY) { + throw new PlatformError('Data provider secret key is missing. Aborting'); } + const ProxyHeaders = { + ...headers, + 'Content-Type': JSON_MIME_TYPE, + TtdSignature: getSignatureHeader(data, config.advertiserSecretKey), + 'TtdSignature-dp': getSignatureHeader( + data, + process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY, + ), + }; + const requestOptions = { url: endpoint, data, @@ -46,7 +37,13 @@ const proxyRequest = async (request) => { headers: ProxyHeaders, method, }; - const response = await httpSend(requestOptions, { feature: 'proxy', destType: 'the_trade_desk' }); + const response = await httpSend(requestOptions, { + feature: 'proxy', + destType: 'the_trade_desk', + endpointPath: '/track/realtimeconversion', + requestMethod: 'POST', + module: 'dataDelivery', + }); return response; }; @@ -70,7 +67,6 @@ const responseHandler = (responseParams) => { // Trade desk first party data api returns 200 with an error in case of "Failed to parse TDID, DAID, UID2, IDL, EUID, or failed to decrypt UID2Token or EUIDToken" // https://partner.thetradedesk.com/v3/portal/data/doc/post-data-advertiser-external // {"FailedLines":[{"ErrorCode":"MissingUserId","Message":"Invalid DAID, item #1"}]} - // For real time conversion api we don't have separate response handling, trade desk always return 400 for bad events. if ('FailedLines' in response && response.FailedLines.length > 0) { throw new AbortedError( `Request failed with status: ${status} due to ${JSON.stringify(response)}`, diff --git a/src/v0/destinations/tiktok_ads/transform.js b/src/v0/destinations/tiktok_ads/transform.js index bdf3a0defe..b8b10d4608 100644 --- a/src/v0/destinations/tiktok_ads/transform.js +++ b/src/v0/destinations/tiktok_ads/transform.js @@ -17,7 +17,6 @@ const { getDestinationExternalID, getFieldValueFromMessage, getHashFromArrayWithDuplicate, - checkInvalidRtTfEvents, handleRtTfSingleEventError, batchMultiplexedEvents, } = require('../../util'); @@ -248,10 +247,6 @@ const processRouterDest = async (inputs, reqMetadata) => { if (Config?.version === 'v2') { return processRouterDestV2(inputs, reqMetadata); } - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const trackResponseList = []; // list containing single track event in batched format const eventsChunk = []; // temporary variable to divide payload into chunks diff --git a/src/v0/destinations/tiktok_ads/transformV2.js b/src/v0/destinations/tiktok_ads/transformV2.js index 91078dfe65..48c5b19e64 100644 --- a/src/v0/destinations/tiktok_ads/transformV2.js +++ b/src/v0/destinations/tiktok_ads/transformV2.js @@ -13,7 +13,6 @@ const { isDefinedAndNotNullAndNotEmpty, getDestinationExternalID, getHashFromArrayWithDuplicate, - checkInvalidRtTfEvents, handleRtTfSingleEventError, } = require('../../util'); const { getContents, hashUserField } = require('./util'); @@ -40,7 +39,7 @@ const getTrackResponsePayload = (message, destConfig, event) => { } // if contents is not present but we have properties.products present which has fields with superset of contents fields - if (payload.properties && !payload.properties.contents && message.properties.products) { + if (!payload.properties?.contents && message.properties?.products) { // retreiving data from products only when contents is not present payload.properties.contents = getContents(message, false); } @@ -690,10 +689,6 @@ const batchEvents = (eventsChunk) => { return events; }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const trackResponseList = []; // list containing single track event in batched format const eventsChunk = []; // temporary variable to divide payload into chunks const errorRespList = []; diff --git a/src/v0/destinations/tiktok_ads_offline_events/config.js b/src/v0/destinations/tiktok_ads_offline_events/config.js index 5d5e80c716..4bb3bda850 100644 --- a/src/v0/destinations/tiktok_ads_offline_events/config.js +++ b/src/v0/destinations/tiktok_ads_offline_events/config.js @@ -19,12 +19,24 @@ const CONFIG_CATEGORIES = { const PARTNER_NAME = 'RudderStack'; const EVENT_NAME_MAPPING = { + addpaymentinfo: 'AddPaymentInfo', + addtocart: 'AddToCart', + addtowishlist: 'AddToWishlist', + 'checkout started': 'InitiateCheckout', 'checkout step completed': 'CompletePayment', + clickbutton: 'ClickButton', + completeregistration: 'CompleteRegistration', contact: 'Contact', + download: 'Download', + 'order completed': 'PlaceAnOrder', + 'payment info entered': 'AddPaymentInfo', + 'product added': 'AddToCart', + 'product added to wishlist': 'AddToWishlist', + search: 'Search', submitform: 'SubmitForm', subscribe: 'Subscribe', + viewcontent: 'ViewContent', }; - const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); module.exports = { diff --git a/src/v0/destinations/tiktok_ads_offline_events/transform.js b/src/v0/destinations/tiktok_ads_offline_events/transform.js index 945c31ea63..dbe9a06dd6 100644 --- a/src/v0/destinations/tiktok_ads_offline_events/transform.js +++ b/src/v0/destinations/tiktok_ads_offline_events/transform.js @@ -9,7 +9,6 @@ const { removeUndefinedAndNullValues, isDefinedAndNotNullAndNotEmpty, getHashFromArrayWithDuplicate, - checkInvalidRtTfEvents, handleRtTfSingleEventError, getSuccessRespEvents, defaultBatchRequestConfig, @@ -199,11 +198,6 @@ const batchEvents = (eventChunksArray) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const batchErrorRespList = []; const eventChunksArray = []; const { destination } = inputs[0]; diff --git a/src/v0/destinations/trengo/transform.js b/src/v0/destinations/trengo/transform.js index 06e5496a1e..01c5cfeb25 100644 --- a/src/v0/destinations/trengo/transform.js +++ b/src/v0/destinations/trengo/transform.js @@ -90,7 +90,13 @@ const lookupContact = async (term, destination) => { Authorization: `Bearer ${destination.Config.apiToken}`, }, }, - { destType: 'trengo', feature: 'transformation' }, + { + destType: 'trengo', + feature: 'transformation', + endpointPath: '/contacts', + requestMethod: 'GET', + module: 'router', + }, ); } catch (err) { // check if exists err.response && err.response.status else 500 diff --git a/src/v0/destinations/twitter_ads/config.js b/src/v0/destinations/twitter_ads/config.js index 601675fc2f..6b0db0622c 100644 --- a/src/v0/destinations/twitter_ads/config.js +++ b/src/v0/destinations/twitter_ads/config.js @@ -14,5 +14,5 @@ const mappingConfig = getMappingConfig(ConfigCategories, __dirname); module.exports = { mappingConfig, ConfigCategories, - BASE_URL + BASE_URL, }; diff --git a/src/v0/destinations/twitter_ads/util.js b/src/v0/destinations/twitter_ads/util.js index 2f237b1dd8..ad59a81267 100644 --- a/src/v0/destinations/twitter_ads/util.js +++ b/src/v0/destinations/twitter_ads/util.js @@ -2,23 +2,20 @@ const crypto = require('crypto'); const oauth1a = require('oauth-1.0a'); function getAuthHeaderForRequest(request, oAuthObject) { - const oauth = oauth1a({ - consumer: { key: oAuthObject.consumerKey, secret: oAuthObject.consumerSecret }, - signature_method: 'HMAC-SHA1', - hash_function(base_string, k) { - return crypto - .createHmac('sha1', k) - .update(base_string) - .digest('base64') - }, - }) + const oauth = oauth1a({ + consumer: { key: oAuthObject.consumerKey, secret: oAuthObject.consumerSecret }, + signature_method: 'HMAC-SHA1', + hash_function(base_string, k) { + return crypto.createHmac('sha1', k).update(base_string).digest('base64'); + }, + }); - const authorization = oauth.authorize(request, { - key: oAuthObject.accessToken, - secret: oAuthObject.accessTokenSecret, - }); + const authorization = oauth.authorize(request, { + key: oAuthObject.accessToken, + secret: oAuthObject.accessTokenSecret, + }); - return oauth.toHeader(authorization); + return oauth.toHeader(authorization); } -module.exports = { getAuthHeaderForRequest }; \ No newline at end of file +module.exports = { getAuthHeaderForRequest }; diff --git a/src/v0/destinations/user/utils.js b/src/v0/destinations/user/utils.js index 52fba2167e..f332d7a4a7 100644 --- a/src/v0/destinations/user/utils.js +++ b/src/v0/destinations/user/utils.js @@ -238,6 +238,8 @@ const createCompany = async (message, destination) => { destType: 'user', feature: 'transformation', endpointPath: `/companies/`, + requestMethod: 'POST', + module: 'router', }); const data = processAxiosResponse(response); return data.response; @@ -279,6 +281,8 @@ const updateCompany = async (message, destination, company) => { destType: 'user', feature: 'transformation', endpointPath: `/companies/`, + requestMethod: 'PUT', + module: 'router', }); const data = processAxiosResponse(response); return data.response; @@ -306,6 +310,8 @@ const getUserByUserKey = async (apiKey, userKey, appSubdomain) => { destType: 'user', feature: 'transformation', endpointPath: `/users/search`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); if (processedUserResponse.status === 200) { @@ -340,6 +346,8 @@ const getUserByEmail = async (apiKey, email, appSubdomain) => { destType: 'user', feature: 'transformation', endpointPath: `/users/search/?email`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); @@ -379,6 +387,8 @@ const getUserByPhoneNumber = async (apiKey, phoneNumber, appSubdomain) => { destType: 'user', feature: 'transformation', endpointPath: `/users/search/?phone_number`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); @@ -424,6 +434,8 @@ const getUserByCustomId = async (message, destination) => { destType: 'user', feature: 'transformation', endpointPath: `/users-by-id/`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); @@ -460,6 +472,8 @@ const getCompanyByCustomId = async (message, destination) => { destType: 'user', feature: 'transformation', endpointPath: `/companies-by-id/`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(response); if (processedUserResponse.status === 200) { diff --git a/src/v0/destinations/wootric/util.js b/src/v0/destinations/wootric/util.js index eb61a472cf..c2505c635b 100644 --- a/src/v0/destinations/wootric/util.js +++ b/src/v0/destinations/wootric/util.js @@ -47,6 +47,8 @@ const getAccessToken = async (destination) => { destType: 'wootric', feature: 'transformation', endpointPath: `/oauth/token`, + requestMethod: 'POST', + module: 'router', }); const processedAuthResponse = processAxiosResponse(wootricAuthResponse); // If the request fails, throwing error. @@ -100,6 +102,8 @@ const retrieveUserDetails = async (endUserId, externalId, accessToken) => { destType: 'wootric', feature: 'transformation', endpointPath: `/v1/end_users/`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); diff --git a/src/v0/destinations/yahoo_dsp/util.js b/src/v0/destinations/yahoo_dsp/util.js index d41716935f..255f84d1c9 100644 --- a/src/v0/destinations/yahoo_dsp/util.js +++ b/src/v0/destinations/yahoo_dsp/util.js @@ -137,6 +137,9 @@ const getAccessToken = async (destination) => { const dspAuthorisationData = await httpSend(request, { destType: 'yahoo_dsp', feature: 'transformation', + endpointPath: '/identity/oauth2/access_token', + requestMethod: 'POST', + module: 'router', }); // If the request fails, throwing error. if (dspAuthorisationData.success === false) { diff --git a/src/v0/destinations/zendesk/transform.js b/src/v0/destinations/zendesk/transform.js index bf2bc01ed2..5862014784 100644 --- a/src/v0/destinations/zendesk/transform.js +++ b/src/v0/destinations/zendesk/transform.js @@ -36,7 +36,7 @@ const tags = require('../../util/tags'); const { JSON_MIME_TYPE } = require('../../util/constant'); const CONTEXT_TRAITS_KEY_PATH = 'context.traits'; - +const endpointPath = '/users/search.json'; function responseBuilder(message, headers, payload, endpoint) { const response = defaultRequestConfig(); @@ -102,6 +102,9 @@ const payloadBuilderforUpdatingEmail = async (userId, headers, userEmail, baseEn const res = await httpGET(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: 'users/userId/identities', + requestMethod: 'POST', + module: 'router', }); if (res?.response?.data?.count > 0) { const { identities } = res.response.data; @@ -147,6 +150,9 @@ async function createUserFields(url, config, newFields, fieldJson) { const response = await myAxios.post(url, fieldData, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/userId/identities', + requestMethod: 'POST', + module: 'router', }); if (response.status !== 201) { logger.debug(`${NAME}:: Failed to create User Field : `, field); @@ -176,6 +182,8 @@ async function checkAndCreateUserFields( const response = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + requestMethod: 'POST', + module: 'router', }); const fields = get(response.data, fieldJson); if (response.data && fields) { @@ -253,6 +261,9 @@ const getUserIdByExternalId = async (message, headers, baseEndpoint) => { const resp = await httpGET(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath, + requestMethod: 'GET', + module: 'router', }); if (resp?.response?.data?.count > 0) { @@ -283,6 +294,9 @@ async function getUserId(message, headers, baseEndpoint, type) { const resp = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath, + requestMethod: 'GET', + module: 'router', }); if (!resp || !resp.data || resp.data.count === 0) { logger.debug(`${NAME}:: User not found`); @@ -307,6 +321,9 @@ async function isUserAlreadyAssociated(userId, orgId, headers, baseEndpoint) { const response = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/userId/organization_memberships.json', + requestMethod: 'GET', + module: 'router', }); if (response?.data?.organization_memberships?.[0]?.organization_id === orgId) { return true; @@ -339,6 +356,9 @@ async function createUser(message, headers, destinationConfig, baseEndpoint, typ const resp = await myAxios.post(url, payload, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/create_or_update.json', + requestMethod: 'POST', + module: 'router', }); if (!resp.data || !resp.data.user || !resp.data.user.id) { @@ -420,6 +440,9 @@ async function createOrganization(message, category, headers, destinationConfig, const resp = await myAxios.post(url, payload, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/organizations/create_or_update.json', + requestMethod: 'POST', + module: 'router', }); if (!resp.data || !resp.data.organization) { @@ -488,6 +511,9 @@ async function processIdentify(message, destinationConfig, headers, baseEndpoint const response = await myAxios.get(membershipUrl, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/userId/organization_memberships.json', + requestMethod: 'GET', + module: 'router', }); if ( response.data && @@ -535,6 +561,9 @@ async function processTrack(message, destinationConfig, headers, baseEndpoint) { const userResponse = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath, + requestMethod: 'GET', + module: 'router', }); if (!get(userResponse, 'data.users.0.id') || userResponse.data.count === 0) { const { zendeskUserId, email } = await createUser( diff --git a/src/v0/sources/formsort/transform.js b/src/v0/sources/formsort/transform.js index 18d7b8fc0e..dd37482bc4 100644 --- a/src/v0/sources/formsort/transform.js +++ b/src/v0/sources/formsort/transform.js @@ -1,18 +1,16 @@ -const path = require("path"); -const fs = require("fs"); -const { generateUUID, isDefinedAndNotNull } = require("../../util"); -const Message = require("../message"); +const path = require('path'); +const fs = require('fs'); +const { generateUUID, isDefinedAndNotNull } = require('../../util'); +const Message = require('../message'); // import mapping json using JSON.parse to preserve object key order -const mapping = JSON.parse( - fs.readFileSync(path.resolve(__dirname, "./mapping.json"), "utf-8") -); +const mapping = JSON.parse(fs.readFileSync(path.resolve(__dirname, './mapping.json'), 'utf-8')); function process(event) { const message = new Message(`Formsort`); // we are setting event type as track always - message.setEventType("track"); + message.setEventType('track'); message.setPropertiesV2(event, mapping); @@ -23,9 +21,9 @@ function process(event) { // setting event Name if (event.finalized) { - message.setEventName("FlowFinalized"); + message.setEventName('FlowFinalized'); } else { - message.setEventName("FlowLoaded"); + message.setEventName('FlowLoaded'); } return message; diff --git a/src/v0/sources/formsort/transform.test.js b/src/v0/sources/formsort/transform.test.js index 9b0d814d6a..e3d686fcef 100644 --- a/src/v0/sources/formsort/transform.test.js +++ b/src/v0/sources/formsort/transform.test.js @@ -1,52 +1,51 @@ const { process } = require('./transform'); it(`Transform.js Tests`, () => { - const data = { - input: { - "answers": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "flow_label": "new-flow-2022-11-25", - "variant_label": "main", - "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", - "finalized": false, - "created_at": "2022-11-25T14:41:22+00:00" + const data = { + input: { + answers: { + yes: true, + enter_email: 'test@user.com', + enter_name: '2022-11-17', + yes_or_no: false, + }, + responder_uuid: '66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7', + flow_label: 'new-flow-2022-11-25', + variant_label: 'main', + variant_uuid: '0828efa7-7215-4e7d-a7ab-6c1079010cea', + finalized: false, + created_at: '2022-11-25T14:41:22+00:00', + }, + output: { + context: { + library: { + name: 'unknown', + version: 'unknown', }, - output: { - "context": { - "library": { - "name": "unknown", - "version": "unknown" - }, - "integration": { - "name": "Formsort" - }, - "page": { - "title": "new-flow-2022-11-25" - }, - "variantLabel": "main", - "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" - }, - "integrations": { - "Formsort": false - }, - "type": "track", - "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "originalTimestamp": "2022-11-25T14:41:22+00:00", - "properties": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "event": "FlowLoaded" - } - }; - const output = process(data.input); - expect(output).toEqual(data.output); - -}); \ No newline at end of file + integration: { + name: 'Formsort', + }, + page: { + title: 'new-flow-2022-11-25', + }, + variantLabel: 'main', + variantUuid: '0828efa7-7215-4e7d-a7ab-6c1079010cea', + }, + integrations: { + Formsort: false, + }, + type: 'track', + userId: '66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7', + originalTimestamp: '2022-11-25T14:41:22+00:00', + properties: { + yes: true, + enter_email: 'test@user.com', + enter_name: '2022-11-17', + yes_or_no: false, + }, + event: 'FlowLoaded', + }, + }; + const output = process(data.input); + expect(output).toEqual(data.output); +}); diff --git a/src/v0/sources/shopify/shopify.util.test.js b/src/v0/sources/shopify/shopify.util.test.js index 9c570dde41..d058db36b5 100644 --- a/src/v0/sources/shopify/shopify.util.test.js +++ b/src/v0/sources/shopify/shopify.util.test.js @@ -1,5 +1,4 @@ -const { getShopifyTopic, -} = require('./util'); +const { getShopifyTopic } = require('./util'); jest.mock('ioredis', () => require('../../../../test/__mocks__/redis')); describe('Shopify Utils Test', () => { describe('Fetching Shopify Topic Test Cases', () => { @@ -58,5 +57,4 @@ describe('Shopify Utils Test', () => { } }); }); - }); diff --git a/src/v0/util/facebookUtils/index.js b/src/v0/util/facebookUtils/index.js index 4c09518559..7fa1e898fe 100644 --- a/src/v0/util/facebookUtils/index.js +++ b/src/v0/util/facebookUtils/index.js @@ -147,7 +147,7 @@ const getContentType = (message, defaultValue, categoryToContent, destinationNam return integrationsObj.contentType; } - let { category } = properties; + let { category } = properties || {}; if (!category) { const { products } = properties; if (products && products.length > 0 && Array.isArray(products) && isObject(products[0])) { diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 49ef39969e..1d952693f2 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -22,6 +22,7 @@ const { PlatformError, TransformationError, OAuthSecretError, + getErrorRespEvents, } = require('@rudderstack/integrations-lib'); const logger = require('../../logger'); const stats = require('../../util/stats'); @@ -33,6 +34,7 @@ const { AUTH_STATUS_INACTIVE, } = require('../../adapters/networkhandler/authConstants'); const { FEATURE_FILTER_CODE, FEATURE_GZIP_SUPPORT } = require('./constant'); +const { CommonUtils } = require('../../util/common'); // ======================================================================== // INLINERS @@ -481,16 +483,6 @@ const getSuccessRespEvents = ( destination, }); -// Router transformer -// Error responses -const getErrorRespEvents = (metadata, statusCode, error, statTags, batched = false) => ({ - metadata, - batched, - statusCode, - error, - statTags, -}); - // ======================================================================== // Error Message UTILITIES // ======================================================================== @@ -1660,7 +1652,7 @@ function getValidDynamicFormConfig( */ const checkInvalidRtTfEvents = (inputs) => { if (!Array.isArray(inputs) || inputs.length === 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); + const respEvents = getErrorRespEvents([], 400, 'Invalid event array'); return [respEvents]; } return []; @@ -1722,11 +1714,6 @@ const handleRtTfSingleEventError = (input, error, reqMetadata) => { * @returns */ const simpleProcessRouterDest = async (inputs, singleTfFunc, reqMetadata, processParams) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const respList = await Promise.all( inputs.map(async (input) => { try { @@ -1754,10 +1741,6 @@ const simpleProcessRouterDest = async (inputs, singleTfFunc, reqMetadata, proces * @returns */ const simpleProcessRouterDestSync = async (inputs, singleTfFunc, reqMetadata, processParams) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const respList = []; // eslint-disable-next-line no-restricted-syntax for (const input of inputs) { @@ -2136,6 +2119,87 @@ const parseConfigArray = (arr, key) => { return arr.map((item) => item[key]); }; +/** + * Finds an existing batch based on metadata JobIds from the provided batch and metadataMap. + * @param {*} batch + * @param {*} metadataMap The map containing metadata items indexed by JobIds. + * @returns + */ +const findExistingBatch = (batch, metadataMap) => { + const existingMetadataItem = batch.metadata.find((metadataItem) => + metadataMap.has(metadataItem.jobId), + ); + return existingMetadataItem ? metadataMap.get(existingMetadataItem.jobId) : null; +}; + +/** + * Removes duplicate metadata within each merged batch object. + * @param {*} mergedBatches An array of merged batch objects. + */ +const removeDuplicateMetadata = (mergedBatches) => { + mergedBatches.forEach((batch) => { + const metadataSet = new Set(); + // eslint-disable-next-line no-param-reassign + batch.metadata = batch.metadata.filter((metadataItem) => { + if (!metadataSet.has(metadataItem.jobId)) { + metadataSet.add(metadataItem.jobId); + return true; + } + return false; + }); + }); +}; + +/** + * Combines batched requests with the same JobIds. + * @param {*} inputBatches The array of batched request objects. + * @returns The combined batched requests with merged JobIds. + * + */ +const combineBatchRequestsWithSameJobIds = (inputBatches) => { + const combineBatches = (batches) => { + const clonedBatches = [...batches]; + const mergedBatches = []; + const metadataMap = new Map(); + + clonedBatches.forEach((batch) => { + const existingBatch = findExistingBatch(batch, metadataMap); + + if (existingBatch) { + // Merge batchedRequests arrays + existingBatch.batchedRequest = [ + ...CommonUtils.toArray(existingBatch.batchedRequest), + ...CommonUtils.toArray(batch.batchedRequest), + ]; + + // Merge metadata + batch.metadata.forEach((metadataItem) => { + if (!metadataMap.has(metadataItem.jobId)) { + metadataMap.set(metadataItem.jobId, existingBatch); + } + existingBatch.metadata.push(metadataItem); + }); + } else { + mergedBatches.push(batch); + batch.metadata.forEach((metadataItem) => { + metadataMap.set(metadataItem.jobId, batch); + }); + } + }); + + // Remove duplicate metadata within each merged object + removeDuplicateMetadata(mergedBatches); + + return mergedBatches; + }; + // We need to run this twice because in first pass some batches might not get merged + // and in second pass they might get merged + // Example: [[{jobID:1}, {jobID:2}], [{jobID:3}], [{jobID:1}, {jobID:3}]] + // 1st pass: [[{jobID:1}, {jobID:2}, {jobID:3}], [{jobID:3}]] + // 2nd pass: [[{jobID:1}, {jobID:2}, {jobID:3}]] + return combineBatches(combineBatches(inputBatches)); +}; + // ======================================================================== // EXPORTS // ======================================================================== @@ -2172,7 +2236,6 @@ module.exports = { getDestinationExternalIDInfoForRetl, getDestinationExternalIDObjectForRetl, getDeviceModel, - getErrorRespEvents, getEventTime, getFieldValueFromMessage, getFirstAndLastName, @@ -2249,4 +2312,7 @@ module.exports = { isNewStatusCodesAccepted, IsGzipSupported, parseConfigArray, + findExistingBatch, + removeDuplicateMetadata, + combineBatchRequestsWithSameJobIds, }; diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js index 1c6b34eca6..4dc6255691 100644 --- a/src/v0/util/index.test.js +++ b/src/v0/util/index.test.js @@ -2,7 +2,12 @@ const { TAG_NAMES } = require('@rudderstack/integrations-lib'); const utilities = require('.'); const { getFuncTestData } = require('../../../test/testHelper'); const { FilteredEventsError } = require('./errorTypes'); -const { hasCircularReference, flattenJson, generateExclusionList } = require('./index'); +const { + hasCircularReference, + flattenJson, + generateExclusionList, + combineBatchRequestsWithSameJobIds, +} = require('./index'); // Names of the utility functions to test const functionNames = [ @@ -166,3 +171,288 @@ describe('generateExclusionList', () => { expect(result).toEqual(expected); }); }); + +describe('Unit test cases for combineBatchRequestsWithSameJobIds', () => { + it('Combine batch request with same jobIds', async () => { + const input = [ + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + metadata: [ + { + jobId: 1, + }, + { + jobId: 4, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint2', + }, + metadata: [ + { + jobId: 3, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + metadata: [ + { + jobId: 5, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 1, + }, + { + jobId: 3, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint2', + }, + metadata: [ + { + jobId: 6, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + ]; + + const expectedOutput = [ + { + batchedRequest: [ + { + endpoint: 'https://endpoint1', + }, + { + endpoint: 'https://endpoint3', + }, + { + endpoint: 'https://endpoint2', + }, + ], + metadata: [ + { + jobId: 1, + }, + { + jobId: 4, + }, + { + jobId: 3, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + metadata: [ + { + jobId: 5, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint2', + }, + metadata: [ + { + jobId: 6, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + ]; + expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); + }); + + it('Each batchRequest contains unique jobIds (no event multiplexing)', async () => { + const input = [ + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + metadata: [ + { + jobId: 1, + }, + { + jobId: 4, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 2, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 5, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + ]; + + const expectedOutput = [ + { + batchedRequest: { + endpoint: 'https://endpoint1', + }, + + metadata: [ + { + jobId: 1, + }, + { + jobId: 4, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 2, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + { + batchedRequest: { + endpoint: 'https://endpoint3', + }, + metadata: [ + { + jobId: 5, + }, + ], + batched: true, + statusCode: 200, + destination: { + Config: { + key: 'value', + }, + }, + }, + ]; + expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); + }); +}); diff --git a/src/v0/util/tags.js b/src/v0/util/tags.js index 18f00f963f..dce8c0a338 100644 --- a/src/v0/util/tags.js +++ b/src/v0/util/tags.js @@ -13,6 +13,7 @@ const TAG_NAMES = { DESTINATION_ID: 'destinationId', WORKSPACE_ID: 'workspaceId', SOURCE_ID: 'sourceId', + STATUS: 'statusCode', }; const MODULES = { @@ -51,7 +52,7 @@ const ERROR_TYPES = { OAUTH_SECRET: 'oAuthSecret', UNSUPPORTED: 'unsupported', REDIS: 'redis', - FILTERED: 'filtered' + FILTERED: 'filtered', }; const METADATA = { diff --git a/src/warehouse/index.js b/src/warehouse/index.js index 3305a52762..b3d1c5e4bc 100644 --- a/src/warehouse/index.js +++ b/src/warehouse/index.js @@ -23,7 +23,7 @@ const whPageColumnMappingRules = require('./config/WHPageConfig.js'); const whScreenColumnMappingRules = require('./config/WHScreenConfig.js'); const whGroupColumnMappingRules = require('./config/WHGroupConfig.js'); const whAliasColumnMappingRules = require('./config/WHAliasConfig.js'); -const {isDataLakeProvider, isBlank} = require('./config/helpers'); +const { isDataLakeProvider, isBlank } = require('./config/helpers'); const { InstrumentationError } = require('@rudderstack/integrations-lib'); const whExtractEventTableColumnMappingRules = require('./config/WHExtractEventTableConfig.js'); diff --git a/src/warehouse/util.js b/src/warehouse/util.js index 11d72bfbfd..b4b22721fd 100644 --- a/src/warehouse/util.js +++ b/src/warehouse/util.js @@ -4,7 +4,7 @@ const get = require('get-value'); const v0 = require('./v0/util'); const v1 = require('./v1/util'); const { PlatformError, InstrumentationError } = require('@rudderstack/integrations-lib'); -const {isBlank} = require('./config/helpers'); +const { isBlank } = require('./config/helpers'); const minTimeInMs = Date.parse('0001-01-01T00:00:00Z'); const maxTimeInMs = Date.parse('9999-12-31T23:59:59.999Z'); diff --git a/test/__mocks__/data/sources/shopify/response.json b/test/__mocks__/data/sources/shopify/response.json index ead25067e6..4eef747b94 100644 --- a/test/__mocks__/data/sources/shopify/response.json +++ b/test/__mocks__/data/sources/shopify/response.json @@ -31,4 +31,4 @@ "shopify_test_set_redis_error": { "itemsHash": "EMPTY" } -} \ No newline at end of file +} diff --git a/test/__tests__/data/customerio_source_input.json b/test/__tests__/data/customerio_source_input.json index 5b825d6a00..769c1b7fd3 100644 --- a/test/__tests__/data/customerio_source_input.json +++ b/test/__tests__/data/customerio_source_input.json @@ -111,9 +111,7 @@ "customer_id": "user-123", "delivery_id": "RAECAAFwnUSneIa0ZXkmq8EdkAM==", "headers": { - "Custom-Header": [ - "custom-value" - ] + "Custom-Header": ["custom-value"] }, "identifiers": { "id": "user-123" @@ -389,4 +387,4 @@ "metric": "delivered", "timestamp": 1585751830 } -] \ No newline at end of file +] diff --git a/test/__tests__/data/customerio_source_output.json b/test/__tests__/data/customerio_source_output.json index 24b964d01b..52df88e833 100644 --- a/test/__tests__/data/customerio_source_output.json +++ b/test/__tests__/data/customerio_source_output.json @@ -648,4 +648,4 @@ "originalTimestamp": "2020-04-01T14:37:10.000Z", "sentAt": "2020-04-01T14:37:10.000Z" } -] \ No newline at end of file +] diff --git a/test/__tests__/data/formsort_source.json b/test/__tests__/data/formsort_source.json index a12d84a98a..d94cfd677b 100644 --- a/test/__tests__/data/formsort_source.json +++ b/test/__tests__/data/formsort_source.json @@ -1,94 +1,94 @@ [ - { - "description": "when we receive finalized as false", - "input": { - "answers": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "flow_label": "new-flow-2022-11-25", - "variant_label": "main", - "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", - "finalized": false, - "created_at": "2022-11-25T14:41:22+00:00" + { + "description": "when we receive finalized as false", + "input": { + "answers": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "flow_label": "new-flow-2022-11-25", + "variant_label": "main", + "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", + "finalized": false, + "created_at": "2022-11-25T14:41:22+00:00" + }, + "output": { + "context": { + "library": { + "name": "unknown", + "version": "unknown" + }, + "integration": { + "name": "Formsort" + }, + "page": { + "title": "new-flow-2022-11-25" }, - "output": { - "context": { - "library": { - "name": "unknown", - "version": "unknown" - }, - "integration": { - "name": "Formsort" - }, - "page": { - "title": "new-flow-2022-11-25" - }, - "variantLabel": "main", - "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" - }, - "integrations": { - "Formsort": false - }, - "type": "track", - "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "originalTimestamp": "2022-11-25T14:41:22+00:00", - "properties": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "event": "FlowLoaded" - } + "variantLabel": "main", + "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" + }, + "integrations": { + "Formsort": false + }, + "type": "track", + "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "originalTimestamp": "2022-11-25T14:41:22+00:00", + "properties": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "event": "FlowLoaded" + } + }, + { + "description": "when we receive finalized as true", + "input": { + "answers": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "flow_label": "new-flow-2022-11-25", + "variant_label": "main", + "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", + "finalized": true, + "created_at": "2022-11-25T14:41:22+00:00" }, - { - "description": "when we receive finalized as true", - "input": { - "answers": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "flow_label": "new-flow-2022-11-25", - "variant_label": "main", - "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", - "finalized": true, - "created_at": "2022-11-25T14:41:22+00:00" + "output": { + "context": { + "library": { + "name": "unknown", + "version": "unknown" + }, + "integration": { + "name": "Formsort" + }, + "page": { + "title": "new-flow-2022-11-25" }, - "output": { - "context": { - "library": { - "name": "unknown", - "version": "unknown" - }, - "integration": { - "name": "Formsort" - }, - "page": { - "title": "new-flow-2022-11-25" - }, - "variantLabel": "main", - "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" - }, - "integrations": { - "Formsort": false - }, - "type": "track", - "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "originalTimestamp": "2022-11-25T14:41:22+00:00", - "properties": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "event": "FlowFinalized" - } + "variantLabel": "main", + "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" + }, + "integrations": { + "Formsort": false + }, + "type": "track", + "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "originalTimestamp": "2022-11-25T14:41:22+00:00", + "properties": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "event": "FlowFinalized" } -] \ No newline at end of file + } +] diff --git a/test/__tests__/data/proxy_input.json b/test/__tests__/data/proxy_input.json index a647238f9f..0d7ff24ab7 100644 --- a/test/__tests__/data/proxy_input.json +++ b/test/__tests__/data/proxy_input.json @@ -263,4 +263,4 @@ "destination": "any" } } -] \ No newline at end of file +] diff --git a/test/__tests__/data/shopify.json b/test/__tests__/data/shopify.json index 0153df4d26..48ca8c75a0 100644 --- a/test/__tests__/data/shopify.json +++ b/test/__tests__/data/shopify.json @@ -4,9 +4,7 @@ "input": { "id": "shopify_test3", "query_parameters": { - "topic": [ - "carts_create" - ] + "topic": ["carts_create"] }, "token": "shopify_test3", "line_items": [], @@ -33,12 +31,8 @@ "description": "Invalid topic", "input": { "query_parameters": { - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] } }, "output": { @@ -50,12 +44,8 @@ "input": { "query_parameters": { "topic": [], - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] } }, "output": { @@ -66,15 +56,9 @@ "description": "Unsupported Event Type", "input": { "query_parameters": { - "topic": [ - "random_event" - ], - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "topic": ["random_event"], + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] } }, "output": { @@ -89,15 +73,9 @@ "description": "Identify Call for customers create event", "input": { "query_parameters": { - "topic": [ - "customers_create" - ], - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "topic": ["customers_create"], + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] }, "id": 5747017285820, "email": "anuraj@rudderstack.com", @@ -256,15 +234,9 @@ "description": "Unsupported checkout event", "input": { "query_parameters": { - "topic": [ - "checkout_delete" - ], - "writeKey": [ - "sample-write-key" - ], - "signature": [ - "rudderstack" - ] + "topic": ["checkout_delete"], + "writeKey": ["sample-write-key"], + "signature": ["rudderstack"] }, "admin_graphql_api_id": "gid://shopify/Fulfillment/4124667937024", "created_at": "2022-01-05T18:13:02+05:30", @@ -292,13 +264,9 @@ "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "Sample001test", - "tracking_numbers": [ - "Sample001test" - ], + "tracking_numbers": ["Sample001test"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", - "tracking_urls": [ - "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530" - ], + "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"], "updated_at": "2022-01-05T18:16:48+05:30" }, "output": { @@ -313,15 +281,9 @@ "description": "Track Call -> Fullfillments updated event", "input": { "query_parameters": { - "topic": [ - "fulfillments_update" - ], - "writeKey": [ - "sample-write-key" - ], - "signature": [ - "rudderstack" - ] + "topic": ["fulfillments_update"], + "writeKey": ["sample-write-key"], + "signature": ["rudderstack"] }, "shipping_address": { "address1": "11 Rani Sankari Lane Patuapara Bhowanipore" @@ -420,13 +382,9 @@ "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "Sample001test", - "tracking_numbers": [ - "Sample001test" - ], + "tracking_numbers": ["Sample001test"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", - "tracking_urls": [ - "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530" - ], + "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"], "updated_at": "2022-01-05T18:16:48+05:30" }, "output": { @@ -462,9 +420,7 @@ "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "Sample001test", - "tracking_numbers": [ - "Sample001test" - ], + "tracking_numbers": ["Sample001test"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", "tracking_urls": [ "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530" @@ -556,4 +512,4 @@ } } } -] \ No newline at end of file +] diff --git a/test/apitests/data_scenarios/destination/batch/failure_batch.json b/test/apitests/data_scenarios/destination/batch/failure_batch.json index 8063bc74a1..6352ca1a11 100644 --- a/test/apitests/data_scenarios/destination/batch/failure_batch.json +++ b/test/apitests/data_scenarios/destination/batch/failure_batch.json @@ -1051,125 +1051,314 @@ }, "output": [ { - "metadata": { - "userId": "<<>>testUser<<>>testUser", - "jobId": 2, - "sourceId": "27O0bmEEx3GgfmEhZHUcPwJQVWC", - "destinationId": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", - "attemptNum": 0, - "receivedAt": "2022-12-24T17:29:00.699+05:30", - "createdAt": "2022-12-24T11:59:03.125Z", - "firstAttemptedAt": "", - "transformAt": "processor", - "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg", - "secret": null, - "jobsT": { - "UUID": "aaa8b7c4-2600-478b-b275-01740e1ef50c", - "JobID": 2, - "UserID": "<<>>testUser<<>>testUser", - "CreatedAt": "2022-12-24T11:59:03.125515Z", - "ExpireAt": "2022-12-24T11:59:03.125515Z", - "CustomVal": "AM", - "EventCount": 1, - "EventPayload": { - "body": { - "XML": {}, - "FORM": {}, - "JSON": { - "events": [ - { - "ip": "[::1]", - "time": 1671883143047, - "library": "rudderstack", - "user_id": "testUser", - "device_id": "anon-id", - "insert_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9", - "event_type": "Product Added", - "session_id": -1, - "user_properties": { - "email": "test.c97@gmail.com", - "phone": "+919876543210", - "gender": "Male", - "lastName": "Rudderlabs", - "firstName": "test" - }, - "event_properties": { - "sku": "F15", - "url": "https://www.website.com/product/path", - "name": "Game", - "brand": "Gamepro", - "price": 13.49, - "coupon": "DISC21", - "variant": "111", - "category": "Games", - "position": 1, - "quantity": 11, - "image_url": "https://www.website.com/product/path.png", - "product_id": "123" + "metadata": [ + { + "userId": "<<>>testUser<<>>testUser", + "jobId": 2, + "sourceId": "27O0bmEEx3GgfmEhZHUcPwJQVWC", + "destinationId": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", + "attemptNum": 0, + "receivedAt": "2022-12-24T17:29:00.699+05:30", + "createdAt": "2022-12-24T11:59:03.125Z", + "firstAttemptedAt": "", + "transformAt": "processor", + "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg", + "secret": null, + "jobsT": { + "UUID": "aaa8b7c4-2600-478b-b275-01740e1ef50c", + "JobID": 2, + "UserID": "<<>>testUser<<>>testUser", + "CreatedAt": "2022-12-24T11:59:03.125515Z", + "ExpireAt": "2022-12-24T11:59:03.125515Z", + "CustomVal": "AM", + "EventCount": 1, + "EventPayload": { + "body": { + "XML": {}, + "FORM": {}, + "JSON": { + "events": [ + { + "ip": "[::1]", + "time": 1671883143047, + "library": "rudderstack", + "user_id": "testUser", + "device_id": "anon-id", + "insert_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9", + "event_type": "Product Added", + "session_id": -1, + "user_properties": { + "email": "test.c97@gmail.com", + "phone": "+919876543210", + "gender": "Male", + "lastName": "Rudderlabs", + "firstName": "test" + }, + "event_properties": { + "sku": "F15", + "url": "https://www.website.com/product/path", + "name": "Game", + "brand": "Gamepro", + "price": 13.49, + "coupon": "DISC21", + "variant": "111", + "category": "Games", + "position": 1, + "quantity": 11, + "image_url": "https://www.website.com/product/path.png", + "product_id": "123" + } } + ], + "api_key": "dummyApiKey", + "options": { + "min_id_length": 1 } - ], - "api_key": "dummyApiKey", - "options": { - "min_id_length": 1 - } + }, + "JSON_ARRAY": {} }, - "JSON_ARRAY": {} + "type": "REST", + "files": {}, + "method": "POST", + "params": {}, + "userId": "anon-id", + "headers": { + "Content-Type": "application/json" + }, + "version": "1", + "endpoint": "https://api2.amplitude.com/2/httpapi" }, - "type": "REST", - "files": {}, - "method": "POST", - "params": {}, - "userId": "anon-id", - "headers": { - "Content-Type": "application/json" + "PayloadSize": 1133, + "LastJobStatus": { + "JobID": 0, + "JobState": "", + "AttemptNum": 0, + "ExecTime": "0001-01-01T00:00:00Z", + "RetryTime": "0001-01-01T00:00:00Z", + "ErrorCode": "", + "ErrorResponse": null, + "Parameters": null, + "WorkspaceId": "" }, - "version": "1", - "endpoint": "https://api2.amplitude.com/2/httpapi" - }, - "PayloadSize": 1133, - "LastJobStatus": { - "JobID": 0, - "JobState": "", - "AttemptNum": 0, - "ExecTime": "0001-01-01T00:00:00Z", - "RetryTime": "0001-01-01T00:00:00Z", - "ErrorCode": "", - "ErrorResponse": null, - "Parameters": null, - "WorkspaceId": "" - }, - "Parameters": { - "record_id": null, - "source_id": "27O0bmEEx3GgfmEhZHUcPwJQVWC", - "event_name": "Product Added", - "event_type": "track", - "message_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9", - "received_at": "2022-12-24T17:29:00.699+05:30", - "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg", - "transform_at": "processor", - "source_job_id": "", - "destination_id": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", - "gateway_job_id": 1, - "source_task_id": "", - "source_batch_id": "", - "source_category": "", - "source_job_run_id": "", - "source_task_run_id": "", - "source_definition_id": "1b6gJdqOPOCadT3cddw8eidV591", - "destination_definition_id": "" + "Parameters": { + "record_id": null, + "source_id": "27O0bmEEx3GgfmEhZHUcPwJQVWC", + "event_name": "Product Added", + "event_type": "track", + "message_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9", + "received_at": "2022-12-24T17:29:00.699+05:30", + "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg", + "transform_at": "processor", + "source_job_id": "", + "destination_id": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", + "gateway_job_id": 1, + "source_task_id": "", + "source_batch_id": "", + "source_category": "", + "source_job_run_id": "", + "source_task_run_id": "", + "source_definition_id": "1b6gJdqOPOCadT3cddw8eidV591", + "destination_definition_id": "" + }, + "WorkspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg" }, - "WorkspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg" - }, - "workerAssignedTime": "2022-12-24T17:29:04.051596+05:30" - }, + "workerAssignedTime": "2022-12-24T17:29:04.051596+05:30" + } + ], "batched": false, "statusCode": 400, "statTags": { "errorCategory": "dataValidation", "errorType": "instrumentation" }, - "error": "Both userId and deviceId cannot be undefined" + "error": "Both userId and deviceId cannot be undefined", + "destination": { + "ID": "2JK3ACpBjq9AmvUbxR1u2pDPSYR", + "Name": "Amplitude-2", + "DestinationDefinition": { + "ID": "1QGzO4fWSyq3lsyFHf4eQAMDSr9", + "Name": "AM", + "DisplayName": "Amplitude", + "Config": { + "destConfig": { + "android": [ + "eventUploadPeriodMillis", + "eventUploadThreshold", + "useNativeSDK", + "enableLocationListening", + "trackSessionEvents", + "useAdvertisingIdForDeviceId" + ], + "defaultConfig": [ + "apiKey", + "groupTypeTrait", + "groupValueTrait", + "trackAllPages", + "trackCategorizedPages", + "trackNamedPages", + "traitsToIncrement", + "traitsToSetOnce", + "traitsToAppend", + "traitsToPrepend", + "trackProductsOnce", + "trackRevenuePerProduct", + "versionName", + "apiSecret", + "residencyServer", + "blacklistedEvents", + "whitelistedEvents", + "eventFilteringOption", + "mapDeviceBrand" + ], + "flutter": [ + "eventUploadPeriodMillis", + "eventUploadThreshold", + "useNativeSDK", + "enableLocationListening", + "trackSessionEvents", + "useAdvertisingIdForDeviceId", + "useIdfaAsDeviceId" + ], + "ios": [ + "eventUploadPeriodMillis", + "eventUploadThreshold", + "useNativeSDK", + "trackSessionEvents", + "useIdfaAsDeviceId" + ], + "reactnative": [ + "eventUploadPeriodMillis", + "eventUploadThreshold", + "useNativeSDK", + "enableLocationListening", + "trackSessionEvents", + "useAdvertisingIdForDeviceId", + "useIdfaAsDeviceId" + ], + "web": [ + "useNativeSDK", + "preferAnonymousIdForDeviceId", + "deviceIdFromUrlParam", + "forceHttps", + "trackGclid", + "trackReferrer", + "saveParamsReferrerOncePerSession", + "trackUtmProperties", + "unsetParamsReferrerOnNewSession", + "batchEvents", + "eventUploadPeriodMillis", + "eventUploadThreshold", + "oneTrustCookieCategories" + ] + }, + "excludeKeys": [], + "includeKeys": [ + "apiKey", + "groupTypeTrait", + "groupValueTrait", + "trackAllPages", + "trackCategorizedPages", + "trackNamedPages", + "traitsToIncrement", + "traitsToSetOnce", + "traitsToAppend", + "traitsToPrepend", + "trackProductsOnce", + "trackRevenuePerProduct", + "preferAnonymousIdForDeviceId", + "deviceIdFromUrlParam", + "forceHttps", + "trackGclid", + "trackReferrer", + "saveParamsReferrerOncePerSession", + "trackUtmProperties", + "unsetParamsReferrerOnNewSession", + "batchEvents", + "eventUploadPeriodMillis", + "eventUploadThreshold", + "versionName", + "enableLocationListening", + "useAdvertisingIdForDeviceId", + "trackSessionEvents", + "useIdfaAsDeviceId", + "blacklistedEvents", + "whitelistedEvents", + "oneTrustCookieCategories", + "eventFilteringOption", + "mapDeviceBrand" + ], + "saveDestinationResponse": true, + "secretKeys": ["apiKey", "apiSecret"], + "supportedMessageTypes": ["alias", "group", "identify", "page", "screen", "track"], + "supportedSourceTypes": [ + "android", + "ios", + "web", + "unity", + "amp", + "cloud", + "warehouse", + "reactnative", + "flutter", + "cordova" + ], + "supportsVisualMapper": true, + "transformAt": "processor", + "transformAtV1": "processor" + }, + "ResponseRules": null + }, + "Config": { + "apiKey": "dummyApiKey", + "apiSecret": "", + "blacklistedEvents": [ + { + "eventName": "" + } + ], + "eventFilteringOption": "disable", + "groupTypeTrait": "", + "groupValueTrait": "", + "mapDeviceBrand": false, + "residencyServer": "standard", + "trackAllPages": false, + "trackCategorizedPages": true, + "trackNamedPages": true, + "trackProductsOnce": false, + "trackRevenuePerProduct": false, + "traitsToAppend": [ + { + "traits": "" + } + ], + "traitsToIncrement": [ + { + "traits": "" + } + ], + "traitsToPrepend": [ + { + "traits": "" + } + ], + "traitsToSetOnce": [ + { + "traits": "" + } + ], + "versionName": "", + "whitelistedEvents": [ + { + "eventName": "" + } + ] + }, + "Enabled": true, + "WorkspaceID": "27O0bhB6p5ehfOWeeZlOSsSDTLg", + "Transformations": [], + "IsProcessorEnabled": true, + "RevisionID": "2JMKUgZX3b8sbtDSZrkUB7okeOY" + } }, { "batchedRequest": { 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/adj/processor/data.ts b/test/integrations/destinations/adj/processor/data.ts index 2c208d0d08..e28a25cf59 100644 --- a/test/integrations/destinations/adj/processor/data.ts +++ b/test/integrations/destinations/adj/processor/data.ts @@ -2179,7 +2179,8 @@ export const data = [ status: 200, body: [ { - error: 'App Token is not present. Please configure your app token from config dashbaord', + error: + 'App Token is not present. Please configure your app token from config dashbaord', statTags: { destType: 'ADJ', errorCategory: 'dataValidation', @@ -2205,24 +2206,24 @@ export const data = [ body: [ { message: { - "type": "track", - "event": "Application Installed", - "sentAt": "2022-09-28T20:14:44.995Z", - "userId": "sample_user_id", - "context": { - "device": { - "id": "sample_device_id", - "type": "android", - "advertisingId": "_sample" + type: 'track', + event: 'Application Installed', + sentAt: '2022-09-28T20:14:44.995Z', + userId: 'sample_user_id', + context: { + device: { + id: 'sample_device_id', + type: 'android', + advertisingId: '_sample', + }, + traits: { + userId: '_sample_uid', + anonymousId: '_sample_anonid', }, - "traits": { - "userId": "_sample_uid", - "anonymousId": "_sample_anonid" - } - }, - "timestamp": "2022-09-28T20:14:43.314Z", - "request_ip": "71.189.106.156", - "originalTimestamp": "2022-09-28T20:14:44.995Z" + }, + timestamp: '2022-09-28T20:14:43.314Z', + request_ip: '71.189.106.156', + originalTimestamp: '2022-09-28T20:14:44.995Z', }, destination: { ID: '1i3Em7GMU9xVEiDlZUN8c88BMS9', @@ -2245,8 +2246,7 @@ export const data = [ }, Config: { appToken: 'testAppToken', - customMappings: [ - { from: 'Application Installed', to: '3fdmll' }], + customMappings: [{ from: 'Application Installed', to: '3fdmll' }], partnerParamsKeys: [ { from: 'key1', to: 'partnerParamKey-1' }, { from: 'key2', to: 'partnerParamKey-2' }, @@ -2277,10 +2277,10 @@ export const data = [ endpoint: 'https://s2s.adjust.com/event', headers: { Accept: '*/*' }, params: { - event_token: "3fdmll", - ip_address: "71.189.106.156", + event_token: '3fdmll', + ip_address: '71.189.106.156', android_id: 'sample_device_id', - gps_adid: "_sample", + gps_adid: '_sample', s2s: 1, app_token: 'testAppToken', environment: 'production', @@ -2294,4 +2294,5 @@ export const data = [ ], }, }, - },]; + }, +]; diff --git a/test/integrations/destinations/af/processor/data.ts b/test/integrations/destinations/af/processor/data.ts index 8b639f45c0..d0fd29b089 100644 --- a/test/integrations/destinations/af/processor/data.ts +++ b/test/integrations/destinations/af/processor/data.ts @@ -45,6 +45,7 @@ export const data = [ destination: { Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'appId' }, Enabled: true, + addPropertiesAtRoot: false, }, }, ], @@ -118,6 +119,7 @@ export const data = [ Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'com.rudderlabs.javascript', + addPropertiesAtRoot: false, }, Enabled: true, }, @@ -305,6 +307,7 @@ export const data = [ Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'com.rudderlabs.javascript', + addPropertiesAtRoot: false, }, Enabled: true, }, @@ -1532,4 +1535,306 @@ export const data = [ }, }, }, + { + name: 'af', + description: 'Place Properties at root level Page Call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }], + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + email: 'testhubspot2@email.com', + name: 'Test Hubspot', + anonymousId: '12345', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-GB', + ip: '0.0.0.0', + os: { name: 'android', version: '' }, + screen: { density: 2 }, + }, + type: 'page', + messageId: 'e8585d9a-7137-4223-b295-68ab1b17dad7', + originalTimestamp: '2019-10-15T09:35:31.289Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { path: '', referrer: '', search: '', title: '', url: '' }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + integrations: { AF: { af_uid: 'afUid' } }, + }, + destination: { + Config: { + devKey: 'ef1d42390426e3f7c90ac78272e74344', + androidAppId: 'com.rudderlabs.javascript', + sharingFilter: 'all', + addPropertiesAtRoot: true, + }, + Enabled: true, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript', + headers: { + 'Content-Type': 'application/json', + authentication: 'ef1d42390426e3f7c90ac78272e74344', + }, + method: 'POST', + params: {}, + body: { + JSON: { + app_version_name: '1.0.0', + bundleIdentifier: 'com.rudderlabs.javascript', + customer_user_id: '12345', + eventValue: '{"path":"","referrer":"","search":"","title":"","url":""}', + eventName: 'page', + appsflyer_id: 'afUid', + os: '', + ip: '0.0.0.0', + sharing_filter: 'all', + }, + XML: {}, + JSON_ARRAY: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'af', + description: 'Place properties at root level track call', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }], + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { email: 'testhubspot2@email.com', name: 'Test Hubspot' }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-GB', + ip: '0.0.0.0', + os: { name: 'android', version: '' }, + screen: { density: 2 }, + }, + type: 'track', + messageId: '08829772-d991-427c-b976-b4c4f4430b4e', + originalTimestamp: '2019-10-15T09:35:31.291Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + event: 'test track event HS', + properties: { user_actual_role: 'system_admin, system_user', user_actual_id: 12345 }, + sentAt: '2019-10-14T11:15:53.296Z', + integrations: { AF: { af_uid: 'afUid' } }, + }, + destination: { + Config: { + devKey: 'ef1d42390426e3f7c90ac78272e74344', + androidAppId: 'com.rudderlabs.javascript', + addPropertiesAtRoot: true, + }, + Enabled: true, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript', + headers: { + 'Content-Type': 'application/json', + authentication: 'ef1d42390426e3f7c90ac78272e74344', + }, + params: {}, + body: { + JSON: { + eventValue: + '{"user_actual_role":"system_admin, system_user","user_actual_id":12345}', + eventName: 'test track event HS', + customer_user_id: '12345', + ip: '0.0.0.0', + os: '', + appsflyer_id: 'afUid', + app_version_name: '1.0.0', + bundleIdentifier: 'com.rudderlabs.javascript', + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'af', + description: 'Place properties at root track call with af data', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Order Completed', + sentAt: '2020-08-14T05:30:30.118Z', + context: { + externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }], + source: 'test', + app: { namespace: 'com.rudderlabs.javascript' }, + os: { name: 'android' }, + traits: { anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1' }, + library: { name: 'rudder-sdk-ruby-sync', version: '1.0.6' }, + }, + messageId: '7208bbb6-2c4e-45bb-bf5b-ad426f3593e9', + timestamp: '2020-08-14T05:30:30.118Z', + properties: { + tax: 2, + total: 27.5, + coupon: 'hasbros', + revenue: 48, + price: 25, + quantity: 2, + currency: 'ZAR', + discount: 2.5, + order_id: '50314b8e9bcf000000000000', + products: [ + { + sku: '45790-32', + url: 'https://www.example.com/product/path', + name: 'Monopoly: 3rd Edition', + price: 19, + category: 'Games', + quantity: 1, + image_url: 'https:///www.example.com/product/path.jpg', + product_id: '507f1f77bcf86cd799439011', + }, + { + sku: '46493-32', + name: 'Uno Card Game', + price: 3, + category: 'Games', + quantity: 2, + product_id: '505bd76785ebb509fc183733', + }, + ], + shipping: 3, + subtotal: 22.5, + affiliation: 'Google Store', + checkout_id: 'fksdjfsdjfisjf9sdfjsd9f', + }, + anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1', + integrations: { AF: { af_uid: 'afUid' } }, + }, + destination: { + Config: { + devKey: 'abcde', + androidAppId: 'com.rudderlabs.javascript', + groupTypeTrait: 'email', + groupValueTrait: 'age', + trackProductsOnce: false, + trackRevenuePerProduct: false, + addPropertiesAtRoot: true, + }, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript', + headers: { 'Content-Type': 'application/json', authentication: 'abcde' }, + params: {}, + body: { + JSON: { + bundleIdentifier: 'com.rudderlabs.javascript', + eventValue: + '{"tax":2,"total":27.5,"coupon":"hasbros","revenue":48,"price":25,"quantity":2,"currency":"ZAR","discount":2.5,"order_id":"50314b8e9bcf000000000000","products":[{"sku":"45790-32","url":"https://www.example.com/product/path","name":"Monopoly: 3rd Edition","price":19,"category":"Games","quantity":1,"image_url":"https:///www.example.com/product/path.jpg","product_id":"507f1f77bcf86cd799439011"},{"sku":"46493-32","name":"Uno Card Game","price":3,"category":"Games","quantity":2,"product_id":"505bd76785ebb509fc183733"}],"shipping":3,"subtotal":22.5,"affiliation":"Google Store","checkout_id":"fksdjfsdjfisjf9sdfjsd9f","af_revenue":48,"af_price":[19,3],"af_quantity":[1,2],"af_order_id":"50314b8e9bcf000000000000","af_content_id":["507f1f77bcf86cd799439011","505bd76785ebb509fc183733"]}', + eventName: 'Order Completed', + eventCurrency: 'ZAR', + eventTime: '2020-08-14T05:30:30.118Z', + appsflyer_id: 'afUid', + }, + XML: {}, + JSON_ARRAY: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/am/batch/data.ts b/test/integrations/destinations/am/batch/data.ts index aa67df06c7..91a17606a9 100644 --- a/test/integrations/destinations/am/batch/data.ts +++ b/test/integrations/destinations/am/batch/data.ts @@ -61,7 +61,7 @@ export const data = [ endpoint: 'https://api.eu.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -83,16 +83,24 @@ export const data = [ { batched: false, error: 'Both userId and deviceId cannot be undefined', - //TODO fix this - metadata: { - job_id: 1, - userId: 'u1', - }, + metadata: [ + { + jobId: 1, + userId: 'u1', + }, + ], statTags: { errorCategory: 'dataValidation', errorType: 'instrumentation', }, statusCode: 400, + destination: { + ID: 'a', + url: 'a', + Config: { + residencyServer: 'EU', + }, + }, }, ], }, @@ -163,7 +171,7 @@ export const data = [ endpoint: 'https://api.eu.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -218,7 +226,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 2, + jobId: 2, userId: 'u1', }, destination: { @@ -273,7 +281,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 3, + jobId: 3, userId: 'u1', }, destination: { @@ -331,7 +339,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 4, + jobId: 4, userId: 'u1', }, destination: { @@ -389,7 +397,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 5, + jobId: 5, userId: 'u1', }, destination: { @@ -423,7 +431,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/groupidentify', }, metadata: { - job_id: 6, + jobId: 6, userId: 'u1', }, destination: { @@ -455,7 +463,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/usermap', }, metadata: { - job_id: 7, + jobId: 7, userId: 'u1', }, destination: { @@ -529,7 +537,7 @@ export const data = [ }, metadata: [ { - job_id: 1, + jobId: 1, userId: 'u1', }, ], @@ -565,7 +573,7 @@ export const data = [ }, metadata: [ { - job_id: 6, + jobId: 6, userId: 'u1', }, ], @@ -599,7 +607,7 @@ export const data = [ }, metadata: [ { - job_id: 7, + jobId: 7, userId: 'u1', }, ], @@ -710,19 +718,19 @@ export const data = [ }, metadata: [ { - job_id: 2, + jobId: 2, userId: 'u1', }, { - job_id: 3, + jobId: 3, userId: 'u1', }, { - job_id: 4, + jobId: 4, userId: 'u1', }, { - job_id: 5, + jobId: 5, userId: 'u1', }, ], @@ -801,7 +809,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -854,7 +862,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 2, + jobId: 2, userId: 'u1', }, destination: { @@ -907,7 +915,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 3, + jobId: 3, userId: 'u1', }, destination: { @@ -963,7 +971,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 4, + jobId: 4, userId: 'u1', }, destination: { @@ -1019,7 +1027,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 5, + jobId: 5, userId: 'u1', }, destination: { @@ -1053,7 +1061,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/groupidentify', }, metadata: { - job_id: 6, + jobId: 6, userId: 'u1', }, destination: { @@ -1085,7 +1093,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/usermap', }, metadata: { - job_id: 7, + jobId: 7, userId: 'u1', }, destination: { @@ -1157,7 +1165,7 @@ export const data = [ }, metadata: [ { - job_id: 1, + jobId: 1, userId: 'u1', }, ], @@ -1193,7 +1201,7 @@ export const data = [ }, metadata: [ { - job_id: 6, + jobId: 6, userId: 'u1', }, ], @@ -1227,7 +1235,7 @@ export const data = [ }, metadata: [ { - job_id: 7, + jobId: 7, userId: 'u1', }, ], @@ -1338,19 +1346,19 @@ export const data = [ }, metadata: [ { - job_id: 2, + jobId: 2, userId: 'u1', }, { - job_id: 3, + jobId: 3, userId: 'u1', }, { - job_id: 4, + jobId: 4, userId: 'u1', }, { - job_id: 5, + jobId: 5, userId: 'u1', }, ], @@ -2117,7 +2125,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -2172,7 +2180,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 2, + jobId: 2, userId: 'u1', }, destination: { @@ -2227,7 +2235,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 3, + jobId: 3, userId: 'u1', }, destination: { @@ -2285,7 +2293,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 4, + jobId: 4, userId: 'u1', }, destination: { @@ -2343,7 +2351,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 5, + jobId: 5, userId: 'u1', }, destination: { @@ -2377,7 +2385,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/groupidentify', }, metadata: { - job_id: 6, + jobId: 6, userId: 'u1', }, destination: { @@ -2409,7 +2417,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/usermap', }, metadata: { - job_id: 7, + jobId: 7, userId: 'u1', }, destination: { @@ -2483,7 +2491,7 @@ export const data = [ }, metadata: [ { - job_id: 1, + jobId: 1, userId: 'u1', }, ], @@ -2519,7 +2527,7 @@ export const data = [ }, metadata: [ { - job_id: 6, + jobId: 6, userId: 'u1', }, ], @@ -2553,7 +2561,7 @@ export const data = [ }, metadata: [ { - job_id: 7, + jobId: 7, userId: 'u1', }, ], @@ -2664,19 +2672,19 @@ export const data = [ }, metadata: [ { - job_id: 2, + jobId: 2, userId: 'u1', }, { - job_id: 3, + jobId: 3, userId: 'u1', }, { - job_id: 4, + jobId: 4, userId: 'u1', }, { - job_id: 5, + jobId: 5, userId: 'u1', }, ], @@ -2756,7 +2764,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 1, + jobId: 1, userId: 'u1', }, destination: { @@ -2811,7 +2819,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 2, + jobId: 2, userId: 'u1', }, destination: { @@ -2866,7 +2874,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 3, + jobId: 3, userId: 'u1', }, destination: { @@ -2924,7 +2932,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 4, + jobId: 4, userId: 'u1', }, destination: { @@ -2982,7 +2990,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/2/httpapi', }, metadata: { - job_id: 5, + jobId: 5, userId: 'u1', }, destination: { @@ -3016,7 +3024,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/groupidentify', }, metadata: { - job_id: 6, + jobId: 6, userId: 'u1', }, destination: { @@ -3048,7 +3056,7 @@ export const data = [ endpoint: 'https://api2.amplitude.com/usermap', }, metadata: { - job_id: 7, + jobId: 7, userId: 'u1', }, destination: { @@ -3121,7 +3129,7 @@ export const data = [ }, metadata: [ { - job_id: 1, + jobId: 1, userId: 'u1', }, ], @@ -3157,7 +3165,7 @@ export const data = [ }, metadata: [ { - job_id: 6, + jobId: 6, userId: 'u1', }, ], @@ -3191,7 +3199,7 @@ export const data = [ }, metadata: [ { - job_id: 7, + jobId: 7, userId: 'u1', }, ], @@ -3302,19 +3310,19 @@ export const data = [ }, metadata: [ { - job_id: 2, + jobId: 2, userId: 'u1', }, { - job_id: 3, + jobId: 3, userId: 'u1', }, { - job_id: 4, + jobId: 4, userId: 'u1', }, { - job_id: 5, + jobId: 5, userId: 'u1', }, ], diff --git a/test/integrations/destinations/am/processor/data.ts b/test/integrations/destinations/am/processor/data.ts index f28606da0c..b645fb5ac7 100644 --- a/test/integrations/destinations/am/processor/data.ts +++ b/test/integrations/destinations/am/processor/data.ts @@ -11327,4 +11327,57 @@ export const data = [ }, }, }, + { + name: 'am', + description: + 'Test 78 -> Page call invalid event type as page name and template is not provided', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + request_ip: '1.1.1.1', + type: 'page', + userId: '12345', + properties: {}, + integrations: { + All: true, + }, + sentAt: '2019-10-14T11:15:53.296Z', + }, + destination: { + Config: { + apiKey: 'abcde', + useUserDefinedPageEventName: true, + userProvidedPageEventString: '', + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 400, + error: + 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'AM', + module: 'destination', + implementation: 'native', + feature: 'processor', + }, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/bluecore/data.ts b/test/integrations/destinations/bluecore/data.ts new file mode 100644 index 0000000000..c2205c25a1 --- /dev/null +++ b/test/integrations/destinations/bluecore/data.ts @@ -0,0 +1,6 @@ +import { ecomTestData } from './ecommTestData'; +import { identifyData } from './identifyTestData'; +import { trackTestData } from './trackTestData'; +import { validationTestData } from './validationTestData'; + +export const data = [...identifyData, ...trackTestData, ...ecomTestData, ...validationTestData]; diff --git a/test/integrations/destinations/bluecore/ecommTestData.ts b/test/integrations/destinations/bluecore/ecommTestData.ts new file mode 100644 index 0000000000..de7584df78 --- /dev/null +++ b/test/integrations/destinations/bluecore/ecommTestData.ts @@ -0,0 +1,489 @@ +import { generateSimplifiedTrackPayload, transformResultBuilder } from '../../testUtils'; + +const metadata = { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', +}; + +const destination = { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + bluecoreNamespace: 'dummy_sandbox', + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + { + from: 'testPurchase', + to: 'purchase', + }, + { + from: 'testboth', + to: 'wishlist', + }, + { + from: 'testboth', + to: 'add_to_cart', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, +}; + +const commonTraits = { + id: 'user@1', + age: '22', + anonymousId: '9c6bd77ea9da3e68', +}; + +const commonPropsWithProducts = { + property1: 'value1', + property2: 'value2', + products: [ + { + product_id: '123', + sku: 'sku123', + name: 'Product 1', + price: 100, + quantity: 2, + }, + { + product_id: '124', + sku: 'sku124', + name: 'Product 2', + price: 200, + quantity: 3, + }, + ], +}; + +const commonPropsWithoutProducts = { + property1: 'value1', + property2: 'value2', + product_id: '123', +}; + +const commonOutputHeaders = { + 'Content-Type': 'application/json', +}; + +const eventEndPoint = 'https://api.bluecore.com/api/track/mobile/v1'; + +export const ecomTestData = [ + { + id: 'bluecore-track-test-1', + name: 'bluecore', + description: + 'Track event call with custom event mapped in destination config to purchase event. This will fail as order_id is not present in the payload', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'testPurchase', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore] property:: order_id is required for purchase event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore] property:: order_id is required for purchase event', + metadata, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-2', + name: 'bluecore', + description: + 'Track event call with custom event mapped in destination config to purchase event. This will fail as total is not present in the payload', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'testPurchase', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: { ...commonPropsWithProducts, order_id: '123' }, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore] property:: total is required for purchase event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore] property:: total is required for purchase event', + metadata, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-3', + name: 'bluecore', + description: + 'Track event call with products searched event not mapped in destination config. This will fail as search_query is not present in the payload', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'Products Searched', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: { ...commonPropsWithoutProducts }, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore] property:: search_query is required for search event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore] property:: search_query is required for search event', + metadata, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-4', + name: 'bluecore', + description: + 'Track event call with Product Viewed event not mapped in destination config. This will be sent with viewed_product name. This event without properties.products will add entire property object as products as this event type is recommended to sent with products', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'product viewed', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + products: [ + { + id: '123', + property1: 'value1', + property2: 'value2', + }, + ], + }, + event: 'viewed_product', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-5', + name: 'bluecore', + description: + 'Track event call with custom event mapped with two standard ecomm events in destination config. Both of the two corresponding standard events will be sent ', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'testboth', + sentAt: '2020-08-14T05:30:30.118Z', + channel: 'web', + context: { + source: 'test', + userAgent: 'chrome', + traits: { + id: 'user@1', + age: '22', + anonymousId: '9c6bd77ea9da3e68', + }, + device: { + advertisingId: 'abc123', + }, + library: { + name: 'rudder-sdk-ruby-sync', + version: '1.0.6', + }, + }, + properties: { + property1: 'value1', + property2: 'value2', + product_id: '123', + }, + anonymousId: 'new-id', + integrations: { + All: true, + }, + }, + metadata, + destination, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'user@1', + customer: { + age: '22', + }, + products: [ + { + id: '123', + property1: 'value1', + property2: 'value2', + }, + ], + }, + event: 'wishlist', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'user@1', + customer: { + age: '22', + }, + products: [ + { + id: '123', + property1: 'value1', + property2: 'value2', + }, + ], + }, + event: 'add_to_cart', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-6', + name: 'bluecore', + description: + 'Track event call with Order Completed event without product array and not mapped in destination config. This will be sent with purchase name. This event without properties.products will generate error as products array is required for purchase event and ordered completed is a standard ecomm event', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'Order Completed', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore]:: products array is required for purchase event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore]:: products array is required for purchase event', + metadata, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bluecore/identifyTestData.ts b/test/integrations/destinations/bluecore/identifyTestData.ts new file mode 100644 index 0000000000..660e335bc6 --- /dev/null +++ b/test/integrations/destinations/bluecore/identifyTestData.ts @@ -0,0 +1,381 @@ +import { + overrideDestination, + transformResultBuilder, + generateSimplifiedIdentifyPayload, +} from '../../testUtils'; + +const destination = { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + bluecoreNamespace: 'dummy_sandbox', + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, +}; + +const metadata = { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', +}; + +const commonTraits = { + anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1', + phone: '+1234589947', + gender: 'non-binary', + db: '19950715', + lastname: 'Rudderlabs', + firstName: 'Test', + address: { + city: 'Kolkata', + state: 'WB', + zip: '700114', + country: 'IN', + }, +}; + +const contextWithExternalId = { + traits: { + ...commonTraits, + email: 'abc@gmail.com', + }, + externalId: [{ type: 'bluecoreExternalId', id: '54321' }], +}; + +const commonOutputCustomerProperties = { + first_name: 'Test', + last_name: 'Rudderlabs', + sex: 'non-binary', + address: { + city: 'Kolkata', + state: 'WB', + zip: '700114', + country: 'IN', + }, +}; + +const commonOutputHeaders = { + 'Content-Type': 'application/json', +}; + +const anonymousId = '97c46c81-3140-456d-b2a9-690d70aaca35'; +const userId = 'user@1'; +const sentAt = '2021-01-03T17:02:53.195Z'; +const originalTimestamp = '2021-01-03T17:02:53.193Z'; +const commonEndpoint = 'https://api.bluecore.com/api/track/mobile/v1'; + +export const identifyData = [ + { + id: 'bluecore-identify-test-1', + name: 'bluecore', + description: + '[Success]: Identify call with all properties, that creates a customer in bluecore by default', + scenario: 'Business', + successCriteria: + 'Response should containt one payload with event name as customer_patch and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: { + traits: { ...commonTraits, email: 'abc@gmail.com' }, + }, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + userId: '', + method: 'POST', + endpoint: commonEndpoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'abc@gmail.com', + customer: { ...commonOutputCustomerProperties, email: 'abc@gmail.com' }, + }, + token: 'dummy_sandbox', + event: 'customer_patch', + }, + }), + metadata: { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-identify-test-2', + name: 'bluecore', + description: + '[Success]: Identify call with all properties,along with action as identify that mandatorily needs email to link distict_id with customer in bluecore', + scenario: 'Business', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: { + traits: commonTraits, + }, + traits: { + action: 'identify', + }, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + '[Bluecore] property:: email is required for identify action: Workflow: procWorkflow, Step: prepareIdentifyPayload, ChildStep: undefined, OriginalError: [Bluecore] property:: email is required for identify action', + metadata: { + destinationId: '', + destinationType: '', + namespace: '', + sourceType: '', + }, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-identify-test-3', + name: 'bluecore', + description: + '[Success]: Identify call with all properties,along with action as random which is not supported by bluecore for identify action', + scenario: 'Business', + successCriteria: + 'Response should containt one payload with event name as identify and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: { + traits: commonTraits, + }, + traits: { + action: 'random', + }, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + "[Bluecore] traits.action must be 'identify' for identify action: Workflow: procWorkflow, Step: prepareIdentifyPayload, ChildStep: undefined, OriginalError: [Bluecore] traits.action must be 'identify' for identify action", + metadata: { + destinationId: '', + destinationType: '', + namespace: '', + sourceType: '', + }, + statTags: { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-identify-test-4', + name: 'bluecore', + description: + '[Success]: Identify call with all properties, that stitches a customer email with distinct_id in bluecore if action is identify and email is present in traits', + scenario: 'Business', + successCriteria: + 'Response should containt one payload with event name as identify and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: { + traits: { ...commonTraits, email: 'abc@gmail.com' }, + }, + traits: { + action: 'identify', + }, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + userId: '', + method: 'POST', + endpoint: commonEndpoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'user@1', + customer: { ...commonOutputCustomerProperties, email: 'abc@gmail.com' }, + }, + token: 'dummy_sandbox', + event: 'identify', + }, + }), + metadata: { + destinationId: '', + destinationType: '', + namespace: '', + sourceType: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-identify-test-5', + name: 'bluecore', + description: + '[Success]: Identify call with all properties and externalId, that creates a customer in bluecore by default, distinct_id is set to externalId value', + scenario: 'Business', + successCriteria: + 'Response should containt one payload with event name as customer_patch and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + metadata, + message: generateSimplifiedIdentifyPayload({ + context: contextWithExternalId, + anonymousId, + userId, + sentAt, + originalTimestamp, + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + userId: '', + method: 'POST', + endpoint: commonEndpoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: '54321', + customer: { ...commonOutputCustomerProperties, email: 'abc@gmail.com' }, + }, + token: 'dummy_sandbox', + event: 'customer_patch', + }, + }), + metadata: { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bluecore/trackTestData.ts b/test/integrations/destinations/bluecore/trackTestData.ts new file mode 100644 index 0000000000..72d48bf93d --- /dev/null +++ b/test/integrations/destinations/bluecore/trackTestData.ts @@ -0,0 +1,439 @@ +import { + generateSimplifiedTrackPayload, + generateTrackPayload, + overrideDestination, + transformResultBuilder, +} from '../../testUtils'; + +const destination = { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + bluecoreNamespace: 'dummy_sandbox', + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + { + from: 'testPurchase', + to: 'purchase', + }, + { + from: 'testboth', + to: 'wishlist', + }, + { + from: 'testboth', + to: 'add_to_cart', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, +}; + +const metadata = { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', +}; + +const commonTraits = { + id: 'user@1', + age: '22', + anonymousId: '9c6bd77ea9da3e68', +}; + +const contextWithExternalId = { + traits: { + ...commonTraits, + email: 'abc@gmail.com', + }, + externalId: [{ type: 'bluecoreExternalId', id: '54321' }], +}; + +const commonPropsWithProducts = { + property1: 'value1', + property2: 'value2', + products: [ + { + product_id: '123', + sku: 'sku123', + name: 'Product 1', + price: 100, + quantity: 2, + }, + { + product_id: '124', + sku: 'sku124', + name: 'Product 2', + price: 200, + quantity: 3, + }, + ], +}; + +const commonPropsWithoutProducts = { + property1: 'value1', + property2: 'value2', + product_id: '123', +}; + +const commonOutputHeaders = { + 'Content-Type': 'application/json', +}; + +const eventEndPoint = 'https://api.bluecore.com/api/track/mobile/v1'; + +export const trackTestData = [ + { + id: 'bluecore-track-test-1', + name: 'bluecore', + description: + 'Track event call with custom event with properties not mapped in destination config. This will be sent with its original name', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'TestEven001', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + products: [ + { + name: 'Product 1', + price: 100, + id: '123', + quantity: 2, + }, + { + name: 'Product 2', + price: 200, + id: '124', + quantity: 3, + }, + ], + }, + event: 'TestEven001', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-2', + name: 'bluecore', + description: + 'Track event call with custom event without properties not mapped in destination config. This will be sent with its original name', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200. As the event paylaod does not contains products, product array will not be sent', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'TestEven001', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + }, + event: 'TestEven001', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-3', + name: 'bluecore', + description: + 'optin event is also considered as a track event, user need to not map it from the UI , it will be sent with the same event name to bluecore', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'optin', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + }, + event: 'optin', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-4', + name: 'bluecore', + description: + 'unsubscribe event is also considered as a track event, user need to not map it from the UI , it will be sent with the same event name to bluecore', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'unsubscribe', + userId: 'sajal12', + context: { + traits: { + ...commonTraits, + email: 'test@rudderstack.com', + phone: '9112340375', + }, + }, + properties: commonPropsWithoutProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: 'test@rudderstack.com', + customer: { + age: '22', + email: 'test@rudderstack.com', + }, + }, + event: 'unsubscribe', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'bluecore-track-test-5', + name: 'bluecore', + description: + 'Track event call with with externalId. This will map externalId to distinct_id in the payload', + scenario: 'Business', + successCriteria: + 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destination, + metadata, + message: generateSimplifiedTrackPayload({ + type: 'track', + event: 'TestEven001', + userId: 'sajal12', + context: contextWithExternalId, + properties: commonPropsWithProducts, + anonymousId: '9c6bd77ea9da3e68', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + endpoint: eventEndPoint, + headers: commonOutputHeaders, + JSON: { + properties: { + distinct_id: '54321', + customer: { + age: '22', + email: 'abc@gmail.com', + }, + products: [ + { + name: 'Product 1', + price: 100, + id: '123', + quantity: 2, + }, + { + name: 'Product 2', + price: 200, + id: '124', + quantity: 3, + }, + ], + }, + event: 'TestEven001', + token: 'dummy_sandbox', + }, + userId: '', + }), + metadata, + statusCode: 200, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/bluecore/validationTestData.ts b/test/integrations/destinations/bluecore/validationTestData.ts new file mode 100644 index 0000000000..5b81f8c95a --- /dev/null +++ b/test/integrations/destinations/bluecore/validationTestData.ts @@ -0,0 +1,150 @@ +const metadata = { + sourceType: '', + destinationType: '', + namespace: '', + destinationId: '', +}; + +const outputStatTags = { + destType: 'BLUECORE', + destinationId: '', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', +}; + +export const validationTestData = [ + { + id: 'bluecore-validation-test-1', + name: 'bluecore', + description: '[Error]: Check for unsupported message type', + scenario: 'Framework', + successCriteria: + 'Response should contain error message and status code should be 400, as we are sending a message type which is not supported by bluecore destination and the error message should be Event type random is not supported', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + bluecoreNamespace: 'dummy_sandbox', + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, + }, + metadata, + message: { + userId: 'user123', + type: 'random', + groupId: 'XUepkK', + traits: { + subscribe: true, + }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: 'email', + }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'message type random is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type random is not supported', + metadata, + statTags: outputStatTags, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'bluecore-validation-test-1', + name: 'bluecore', + description: '[Error]: Check for not finding bluecoreNamespace', + scenario: 'Framework', + successCriteria: + 'Response should contain error message and status code should be 400, as bluecoreNamespace is not found in the destination config', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'BLUECORE', + Config: { + eventsMapping: [ + { + from: 'ABC Searched', + to: 'search', + }, + ], + }, + Enabled: true, + Transformations: [], + DestinationDefinition: { Config: { cdkV2Enabled: true } }, + }, + metadata: metadata, + message: { + userId: 'user123', + type: 'random', + groupId: 'XUepkK', + traits: { + subscribe: true, + }, + context: { + traits: { + email: 'test@rudderstack.com', + phone: '+12 345 678 900', + consent: 'email', + }, + }, + timestamp: '2020-01-21T00:21:34.208Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'message type random is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type random is not supported', + metadata, + statTags: outputStatTags, + statusCode: 400, + }, + ], + }, + }, + }, +]; 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/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/destinations/clevertap/network.ts b/test/integrations/destinations/clevertap/network.ts index c4eb23ee39..57a647e684 100644 --- a/test/integrations/destinations/clevertap/network.ts +++ b/test/integrations/destinations/clevertap/network.ts @@ -65,7 +65,8 @@ const dataDeliveryMocksData = [ method: 'POST', }, httpRes: { - data: { status: 'fail', error: 'Invalid Credentials', code: 401 }, status: 401 + data: { status: 'fail', error: 'Invalid Credentials', code: 401 }, + status: 401, }, }, { diff --git a/test/integrations/destinations/clevertap/processor/data.ts b/test/integrations/destinations/clevertap/processor/data.ts index d79ebaa8da..6309c5ec8a 100644 --- a/test/integrations/destinations/clevertap/processor/data.ts +++ b/test/integrations/destinations/clevertap/processor/data.ts @@ -52,6 +52,7 @@ export const data = [ state: 'WB', street: '', }, + 'category-unsubscribe': { email: ['Marketing', 'Transactional'] }, }, integrations: { All: true, @@ -98,10 +99,11 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', + 'category-unsubscribe': { email: ['Marketing', 'Transactional'] }, }, identity: 'anon_id', }, @@ -242,8 +244,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -968,8 +970,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -1111,8 +1113,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -1692,8 +1694,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -2077,8 +2079,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', first_name: 'John', @@ -2230,8 +2232,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', first_name: 'John', diff --git a/test/integrations/destinations/clevertap/router/data.ts b/test/integrations/destinations/clevertap/router/data.ts index 4f1723a7da..5f25bbe83e 100644 --- a/test/integrations/destinations/clevertap/router/data.ts +++ b/test/integrations/destinations/clevertap/router/data.ts @@ -162,10 +162,10 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', - custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', + custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', }, objectId: 'anon_id', }, diff --git a/test/integrations/destinations/clickup/network.ts b/test/integrations/destinations/clickup/network.ts index 1a26209923..2cb7cde34f 100644 --- a/test/integrations/destinations/clickup/network.ts +++ b/test/integrations/destinations/clickup/network.ts @@ -1,247 +1,247 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.clickup.com/api/v2/list/correctListId123/field', - method: 'GET', - }, - httpRes: { - data: { - "fields": [ - { - "id": "19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e", - "name": "Labels", - "type": "labels", - "type_config": { - "options": [ - { - "id": "32c81c1c-cf53-4829-92f5-0f0270d27a45", - "label": "Option 1", - "color": {} - }, - { - "id": "7e24f329-9dd9-4e68-b426-2c70af6f9347", - "label": "Option 2", - "color": {} - } - ] - }, - "date_created": "1661964865880", - "hide_from_guests": false, - "required": false - }, - { - "id": "22eaffee-ffec-4c3b-bdae-56e69d55eecd", - "name": "Payment Status", - "type": "drop_down", - "type_config": { - "default": 0, - "placeholder": {}, - "new_drop_down": true, - "options": [ - { - "id": "e109e36b-a052-4a31-af16-25da7324990f", - "name": "Sent Request", - "color": "#FF7FAB", - "orderindex": 0 - }, - { - "id": "3a3b4512-2896-44f7-8075-2ff37777fe24", - "name": "Quote sent", - "color": "#EA80FC", - "orderindex": 1 - }, - { - "id": "7afcb6fb-cec8-41d8-bf0c-039a9db28460", - "name": "Pending", - "color": "#ff7800", - "orderindex": 2 - }, - { - "id": "890ecf28-bdd4-4f53-92cc-bc4edb696fcd", - "name": "Payment Recieved", - "color": "#2ecd6f", - "orderindex": 3 - }, - { - "id": "e89f7dd7-fd24-4b32-ac4d-f174d8ca914f", - "name": "n/a", - "color": "#b5bcc2", - "orderindex": 4 - } - ] - }, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "4b7a29be-e261-4340-8f3f-e6de838473e5", - "name": "Plan", - "type": "drop_down", - "type_config": { - "default": 0, - "placeholder": {}, - "new_drop_down": true, - "options": [ - { - "id": "4b9366a7-2592-4b7a-909a-ed4af705e27c", - "name": "Unlimited", - "color": "#02BCD4", - "orderindex": 0 - }, - { - "id": "c5032049-8c05-44e9-a000-3a071d457b8f", - "name": "Business", - "color": "#1bbc9c", - "orderindex": 1 - }, - { - "id": "9fb08801-1130-4650-8e2e-28578344ff3c", - "name": "Enterprise", - "color": "#2ecd6f", - "orderindex": 2 - } - ] - }, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "4bfebc00-9d4a-40d1-aef8-5a87b610186c", - "name": "Contact Title", - "type": "text", - "type_config": {}, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "666f74bf-6d87-41f3-8735-ccf0efe066dd", - "name": "Date", - "type": "date", - "type_config": {}, - "date_created": "1662379321069", - "hide_from_guests": false, - "required": false - }, - { - "id": "a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c", - "name": "Industry", - "type": "drop_down", - "type_config": { - "default": 0, - "placeholder": {}, - "options": [ - { - "id": "75173398-257f-42b6-8bae-4cf767fa99ab", - "name": "Engineering", - "color": "#04A9F4", - "orderindex": 0 - }, - { - "id": "c7f9b6f5-cd98-4609-af10-68a8710cc1bf", - "name": "Retail", - "color": "#ff7800", - "orderindex": 1 - }, - { - "id": "dbe84940-b4e8-4a29-8491-e1aa5f2be4e2", - "name": "Hospitality", - "color": "#2ecd6f", - "orderindex": 2 - } - ] - }, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "b01b32fd-94d3-43e6-9f31-2c855ff169cd", - "name": "Url", - "type": "url", - "type_config": {}, - "date_created": "1661970432587", - "hide_from_guests": false, - "required": false - }, - { - "id": "c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6", - "name": "Phone Number", - "type": "phone", - "type_config": {}, - "date_created": "1661970795061", - "hide_from_guests": false, - "required": false - }, - { - "id": "d0201829-ddcd-4b97-b71f-0f9e672488f2", - "name": "Account Size", - "type": "number", - "type_config": {}, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "ea6c1e48-2abf-4328-b228-79c213e147c8", - "name": "Location", - "type": "location", - "type_config": {}, - "date_created": "1662229589329", - "hide_from_guests": false, - "required": false - }, - { - "id": "ebe825fb-92de-41ce-a29c-25018da039b4", - "name": "Email", - "type": "email", - "type_config": {}, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "f431cda3-a575-4a05-ba8d-583d9b6cb2df", - "name": "Rating", - "type": "emoji", - "type_config": { - "count": 5, - "code_point": "2b50" - }, - "date_created": "1661963909454", - "hide_from_guests": false, - "required": false - }, - { - "id": "ffbe4f03-cbc3-4077-8fea-9e5d08b4dceb", - "name": "Money In INR", - "type": "currency", - "type_config": { - "default": {}, - "precision": 2, - "currency_type": "INR" - }, - "date_created": "1661428276019", - "hide_from_guests": false, - "required": false - } - ] - }, - status: 200 - }, + { + httpReq: { + url: 'https://api.clickup.com/api/v2/list/correctListId123/field', + method: 'GET', }, - { - httpReq: { - url: 'https://api.clickup.com/api/v2/list/correctListId456/field', - method: 'GET', - }, - httpRes: { - data: { - "fields": [] + httpRes: { + data: { + fields: [ + { + id: '19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e', + name: 'Labels', + type: 'labels', + type_config: { + options: [ + { + id: '32c81c1c-cf53-4829-92f5-0f0270d27a45', + label: 'Option 1', + color: {}, + }, + { + id: '7e24f329-9dd9-4e68-b426-2c70af6f9347', + label: 'Option 2', + color: {}, + }, + ], + }, + date_created: '1661964865880', + hide_from_guests: false, + required: false, + }, + { + id: '22eaffee-ffec-4c3b-bdae-56e69d55eecd', + name: 'Payment Status', + type: 'drop_down', + type_config: { + default: 0, + placeholder: {}, + new_drop_down: true, + options: [ + { + id: 'e109e36b-a052-4a31-af16-25da7324990f', + name: 'Sent Request', + color: '#FF7FAB', + orderindex: 0, + }, + { + id: '3a3b4512-2896-44f7-8075-2ff37777fe24', + name: 'Quote sent', + color: '#EA80FC', + orderindex: 1, + }, + { + id: '7afcb6fb-cec8-41d8-bf0c-039a9db28460', + name: 'Pending', + color: '#ff7800', + orderindex: 2, + }, + { + id: '890ecf28-bdd4-4f53-92cc-bc4edb696fcd', + name: 'Payment Recieved', + color: '#2ecd6f', + orderindex: 3, + }, + { + id: 'e89f7dd7-fd24-4b32-ac4d-f174d8ca914f', + name: 'n/a', + color: '#b5bcc2', + orderindex: 4, + }, + ], + }, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: '4b7a29be-e261-4340-8f3f-e6de838473e5', + name: 'Plan', + type: 'drop_down', + type_config: { + default: 0, + placeholder: {}, + new_drop_down: true, + options: [ + { + id: '4b9366a7-2592-4b7a-909a-ed4af705e27c', + name: 'Unlimited', + color: '#02BCD4', + orderindex: 0, + }, + { + id: 'c5032049-8c05-44e9-a000-3a071d457b8f', + name: 'Business', + color: '#1bbc9c', + orderindex: 1, + }, + { + id: '9fb08801-1130-4650-8e2e-28578344ff3c', + name: 'Enterprise', + color: '#2ecd6f', + orderindex: 2, + }, + ], + }, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: '4bfebc00-9d4a-40d1-aef8-5a87b610186c', + name: 'Contact Title', + type: 'text', + type_config: {}, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: '666f74bf-6d87-41f3-8735-ccf0efe066dd', + name: 'Date', + type: 'date', + type_config: {}, + date_created: '1662379321069', + hide_from_guests: false, + required: false, + }, + { + id: 'a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c', + name: 'Industry', + type: 'drop_down', + type_config: { + default: 0, + placeholder: {}, + options: [ + { + id: '75173398-257f-42b6-8bae-4cf767fa99ab', + name: 'Engineering', + color: '#04A9F4', + orderindex: 0, + }, + { + id: 'c7f9b6f5-cd98-4609-af10-68a8710cc1bf', + name: 'Retail', + color: '#ff7800', + orderindex: 1, + }, + { + id: 'dbe84940-b4e8-4a29-8491-e1aa5f2be4e2', + name: 'Hospitality', + color: '#2ecd6f', + orderindex: 2, + }, + ], + }, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: 'b01b32fd-94d3-43e6-9f31-2c855ff169cd', + name: 'Url', + type: 'url', + type_config: {}, + date_created: '1661970432587', + hide_from_guests: false, + required: false, + }, + { + id: 'c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6', + name: 'Phone Number', + type: 'phone', + type_config: {}, + date_created: '1661970795061', + hide_from_guests: false, + required: false, + }, + { + id: 'd0201829-ddcd-4b97-b71f-0f9e672488f2', + name: 'Account Size', + type: 'number', + type_config: {}, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: 'ea6c1e48-2abf-4328-b228-79c213e147c8', + name: 'Location', + type: 'location', + type_config: {}, + date_created: '1662229589329', + hide_from_guests: false, + required: false, + }, + { + id: 'ebe825fb-92de-41ce-a29c-25018da039b4', + name: 'Email', + type: 'email', + type_config: {}, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: 'f431cda3-a575-4a05-ba8d-583d9b6cb2df', + name: 'Rating', + type: 'emoji', + type_config: { + count: 5, + code_point: '2b50', }, - status: 200 - }, - } + date_created: '1661963909454', + hide_from_guests: false, + required: false, + }, + { + id: 'ffbe4f03-cbc3-4077-8fea-9e5d08b4dceb', + name: 'Money In INR', + type: 'currency', + type_config: { + default: {}, + precision: 2, + currency_type: 'INR', + }, + date_created: '1661428276019', + hide_from_guests: false, + required: false, + }, + ], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.clickup.com/api/v2/list/correctListId456/field', + method: 'GET', + }, + httpRes: { + data: { + fields: [], + }, + status: 200, + }, + }, ]; 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 }, }, diff --git a/test/integrations/destinations/custify/deleteUsers/data.ts b/test/integrations/destinations/custify/deleteUsers/data.ts new file mode 100644 index 0000000000..22a120770a --- /dev/null +++ b/test/integrations/destinations/custify/deleteUsers/data.ts @@ -0,0 +1,158 @@ +const destType = 'custify'; +const commonData = { + name: destType, + feature: 'userDeletion', + module: 'destination', + version: 'v0', +}; + +export const data = [ + { + description: 'Test 0: should fail when config is not being sent', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + ], + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'Config for deletion not present', + }, + ], + }, + }, + }, + { + description: 'Test 1: should fail when apiKey is not present in config', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder2', + }, + ], + config: { + apiToken: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'api key for deletion not present', + }, + ], + }, + }, + }, + + { + description: 'Test 2: should pass when one of the users is not present in destination', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + userId: 'rudder2', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [{ statusCode: 200, status: 'successful' }], + }, + }, + }, + + { + description: + 'Test 3: should fail when one of the users is returning with 4xx(not 404) from destination', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + userId: 'rudder3', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [{ statusCode: 400, error: '{"error":"User: rudder3 has a problem"}' }], + }, + }, + }, + + { + description: 'Test 4: should fail when one of the userAttributes does not contain `userId`', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + {}, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [{ statusCode: 400, error: 'User id for deletion not present' }], + }, + }, + }, +].map((props) => ({ ...commonData, ...props })); diff --git a/test/integrations/destinations/custify/network.ts b/test/integrations/destinations/custify/network.ts index 4af6545f9f..242f54c97b 100644 --- a/test/integrations/destinations/custify/network.ts +++ b/test/integrations/destinations/custify/network.ts @@ -1,36 +1,85 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.custify.com/company', - method: 'POST', + { + httpReq: { + url: 'https://api.custify.com/company', + method: 'POST', + }, + httpRes: { + data: { + company_id: '6', + name: 'Pizzeria Presto', + signed_up_at: '2019-05-30T12:00:00.000Z', + size: 15, + website: 'www.pizzeriapresto.com', + industry: 'Restaurant', + plan: 'Platinum', + monthly_revenue: 1234567, + churned: false, + owners_csm: 'john.doe@mail.com', + owners_account: 'john.doe@mail.com', + parent_companies: [ + { + id: '5ec50c9829d3c17c7cf455f2', + }, + { + id: '5ec50c9829d3c17c7cf457f2', + }, + ], + custom_attributes: { + restaurants: 5, + custom: 'template', }, - httpRes: { - data: { - "company_id": "6", - "name": "Pizzeria Presto", - "signed_up_at": "2019-05-30T12:00:00.000Z", - "size": 15, - "website": "www.pizzeriapresto.com", - "industry": "Restaurant", - "plan": "Platinum", - "monthly_revenue": 1234567, - "churned": false, - "owners_csm": "john.doe@mail.com", - "owners_account": "john.doe@mail.com", - "parent_companies": [ - { - "id": "5ec50c9829d3c17c7cf455f2" - }, - { - "id": "5ec50c9829d3c17c7cf457f2" - } - ], - "custom_attributes": { - "restaurants": 5, - "custom": "template" - } - }, - status: 200 - }, - } + }, + status: 200, + }, + }, + + { + httpReq: { + method: 'delete', + url: 'https://api.custify.com/people?user_id=rudder1', + headers: { + Authorization: 'Bearer dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder1 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.custify.com/people?user_id=rudder2', + headers: { + Authorization: 'Bearer dummyApiKey', + }, + }, + httpRes: { + data: { + error: 'User: rudder2 not found', + }, + status: 404, + }, + }, + + { + httpReq: { + method: 'delete', + url: 'https://api.custify.com/people?user_id=rudder3', + headers: { + Authorization: 'Bearer dummyApiKey', + }, + }, + httpRes: { + data: { + error: 'User: rudder3 has a problem', + }, + status: 400, + }, + }, ]; diff --git a/test/integrations/destinations/delighted/network.ts b/test/integrations/destinations/delighted/network.ts index 15b0a414e6..d9896a25e8 100644 --- a/test/integrations/destinations/delighted/network.ts +++ b/test/integrations/destinations/delighted/network.ts @@ -1,30 +1,30 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.delighted.com/v1/people.json', - method: 'GET', - headers: { Authorization: `Basic ZHVtbXlBcGlLZXk=` }, - params: { - email: "identified_user@email.com" - } - }, - httpRes: { - data: ["user data"], - status: 200 - }, + { + httpReq: { + url: 'https://api.delighted.com/v1/people.json', + method: 'GET', + headers: { Authorization: `Basic ZHVtbXlBcGlLZXk=` }, + params: { + email: 'identified_user@email.com', + }, }, - { - httpReq: { - url: 'https://api.delighted.com/v1/people.json', - method: 'GET', - headers: { Authorization: `Basic ZHVtbXlBcGlLZXlmb3JmYWlsdXJl` }, - params: { - email: "unidentified_user@email.com" - } - }, - httpRes: { - data: [], - status: 200 - }, - } + httpRes: { + data: ['user data'], + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.delighted.com/v1/people.json', + method: 'GET', + headers: { Authorization: `Basic ZHVtbXlBcGlLZXlmb3JmYWlsdXJl` }, + params: { + email: 'unidentified_user@email.com', + }, + }, + httpRes: { + data: [], + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/fb_custom_audience/network.ts b/test/integrations/destinations/fb_custom_audience/network.ts index fa11f28370..9b498bc07e 100644 --- a/test/integrations/destinations/fb_custom_audience/network.ts +++ b/test/integrations/destinations/fb_custom_audience/network.ts @@ -512,14 +512,15 @@ export const networkCallsData = [ httpRes: { data: { error: { - message: 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', + message: + 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', type: 'OAuthException', code: 190, error_subcode: 463, - fbtrace_id: 'A3b8C6PpI-kdIOwPwV4PANi' + fbtrace_id: 'A3b8C6PpI-kdIOwPwV4PANi', }, }, status: 400, }, - } + }, ]; diff --git a/test/integrations/destinations/freshmarketer/network.ts b/test/integrations/destinations/freshmarketer/network.ts index 51f1a0c115..9d661f2686 100644 --- a/test/integrations/destinations/freshmarketer/network.ts +++ b/test/integrations/destinations/freshmarketer/network.ts @@ -1,487 +1,495 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "sales_account": { - "id": 70003771396, - "name": "postman2.0", - "address": "Red Colony", - "city": "Pune", - "state": "Goa", - "zipcode": null, - "country": null, - "number_of_employees": 11, - "annual_revenue": 1000, - "website": null, - "owner_id": null, - "phone": "919191919191", - "open_deals_amount": null, - "open_deals_count": null, - "won_deals_amount": null, - "won_deals_count": null, - "last_contacted": null, - "last_contacted_mode": null, - "facebook": null, - "twitter": null, - "linkedin": null, - "links": { - "conversations": "/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "document_associations": "/crm/sales/sales_accounts/70003771396/document_associations", - "notes": "/crm/sales/sales_accounts/70003771396/notes?include=creater", - "tasks": "/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note" - }, - "custom_field": {}, - "created_at": "2022-08-17T04:15:00-04:00", - "updated_at": "2022-08-24T06:03:31-04:00", - "avatar": null, - "parent_sales_account_id": null, - "recent_note": null, - "last_contacted_via_sales_activity": null, - "last_contacted_sales_activity_mode": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "last_assigned_at": null, - "tags": [], - "is_deleted": false, - "team_user_ids": null, - "has_connections": false, - "record_type_id": "71010794477" - } - }, - status: 200 - }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70042006456, - "first_name": "Rk", - "last_name": "Mishra", - "display_name": "Rk Mishra", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "testuser@google.com", - "emails": [ - { - "id": 70037311213, - "value": "testuser@google.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": "IST", - "work_number": "9988776655", - "mobile_number": "19265559504", - "address": null, - "last_seen": null, - "lead_score": 26, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70042006456/timeline_feeds", - "document_associations": "/crm/sales/contacts/70042006456/document_associations", - "notes": "/crm/sales/contacts/70042006456/notes?include=creater", - "tasks": "/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70042006456/duplicates", - "connections": "/crm/sales/contacts/70042006456/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-08-09T03:22:12-04:00", - "updated_at": "2022-08-30T00:33:27-04:00", - "keyword": "drilling", - "medium": "facebook", - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": "2022-08-29T05:51:24-04:00", - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": "ea5cfab2-3961-4d8a-8187-3d1858c99099", - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 2, - "record_type_id": "71010794476", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [], - "sales_accounts": [ - { - "partial": true, - "id": 70003771198, - "name": "div-quer", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": true - }, - { - "partial": true, - "id": 70003825177, - "name": "BisleriGroup", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": false - } - ] - } - }, - status: 200 + httpRes: { + data: { + sales_account: { + id: 70003771396, + name: 'postman2.0', + address: 'Red Colony', + city: 'Pune', + state: 'Goa', + zipcode: null, + country: null, + number_of_employees: 11, + annual_revenue: 1000, + website: null, + owner_id: null, + phone: '919191919191', + open_deals_amount: null, + open_deals_count: null, + won_deals_amount: null, + won_deals_count: null, + last_contacted: null, + last_contacted_mode: null, + facebook: null, + twitter: null, + linkedin: null, + links: { + conversations: + '/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + document_associations: '/crm/sales/sales_accounts/70003771396/document_associations', + notes: '/crm/sales/sales_accounts/70003771396/notes?include=creater', + tasks: + '/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + }, + custom_field: {}, + created_at: '2022-08-17T04:15:00-04:00', + updated_at: '2022-08-24T06:03:31-04:00', + avatar: null, + parent_sales_account_id: null, + recent_note: null, + last_contacted_via_sales_activity: null, + last_contacted_sales_activity_mode: null, + completed_sales_sequences: null, + active_sales_sequences: null, + last_assigned_at: null, + tags: [], + is_deleted: false, + team_user_ids: null, + has_connections: false, + record_type_id: '71010794477', }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', - method: 'GET' - }, - httpRes: { - data: { - "sales_activity_types": [ - { - "partial": true, - "id": 70000666879, - "name": "own-calender", - "internal_name": "cappointment", - "show_in_conversation": true, - "position": 1, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663932, - "name": "fb-support", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 2, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663746, - "name": "twitter sales", - "internal_name": "twitter", - "show_in_conversation": true, - "position": 3, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000646396, - "name": "linked sales", - "internal_name": "linkedin", - "show_in_conversation": true, - "position": 4, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000642330, - "name": "facebook sales", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 5, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612897, - "name": "Chat", - "internal_name": "chat", - "show_in_conversation": true, - "position": 6, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612898, - "name": "Phone", - "internal_name": "phone", - "show_in_conversation": true, - "position": 7, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612899, - "name": "Meeting", - "internal_name": "appointment", - "show_in_conversation": true, - "position": 8, - "is_default": true, - "is_checkedin": true - }, - { - "partial": true, - "id": 70000612900, - "name": "Task", - "internal_name": "task", - "show_in_conversation": true, - "position": 9, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612901, - "name": "Email", - "internal_name": "email", - "show_in_conversation": true, - "position": 10, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612902, - "name": "SMS Outgoing", - "internal_name": "sms_outgoing", - "show_in_conversation": true, - "position": 11, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612903, - "name": "Reminder", - "internal_name": "reminder", - "show_in_conversation": false, - "position": 12, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612904, - "name": "SMS Incoming", - "internal_name": "sms_incoming", - "show_in_conversation": true, - "position": 13, - "is_default": true, - "is_checkedin": false - } - ] - }, - status: 200 - }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70054866612, - "first_name": null, - "last_name": null, - "display_name": "jamessampleton120@gmail.com", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "jamessampleton120@gmail.com", - "emails": [ - { - "id": 70047409219, - "value": "jamessampleton120@gmail.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": null, - "work_number": null, - "mobile_number": null, - "address": null, - "last_seen": null, - "lead_score": 0, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70054866612/timeline_feeds", - "document_associations": "/crm/sales/contacts/70054866612/document_associations", - "notes": "/crm/sales/contacts/70054866612/notes?include=creater", - "tasks": "/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70054866612/duplicates", - "connections": "/crm/sales/contacts/70054866612/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-10-11T08:42:15-04:00", - "updated_at": "2022-10-11T08:42:15-04:00", - "keyword": null, - "medium": null, - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": null, - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": null, - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 0, - "record_type_id": "71012139284", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [] - } + httpRes: { + data: { + contact: { + id: 70042006456, + first_name: 'Rk', + last_name: 'Mishra', + display_name: 'Rk Mishra', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'testuser@google.com', + emails: [ + { + id: 70037311213, + value: 'testuser@google.com', + is_primary: true, + label: null, + _destroy: false, + }, + ], + time_zone: 'IST', + work_number: '9988776655', + mobile_number: '19265559504', + address: null, + last_seen: null, + lead_score: 26, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70042006456/timeline_feeds', + document_associations: '/crm/sales/contacts/70042006456/document_associations', + notes: '/crm/sales/contacts/70042006456/notes?include=creater', + tasks: + '/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70042006456/duplicates', + connections: '/crm/sales/contacts/70042006456/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-08-09T03:22:12-04:00', + updated_at: '2022-08-30T00:33:27-04:00', + keyword: 'drilling', + medium: 'facebook', + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: '2022-08-29T05:51:24-04:00', + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 2, + record_type_id: '71010794476', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], + sales_accounts: [ + { + partial: true, + id: 70003771198, + name: 'div-quer', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: true, + }, + { + partial: true, + id: 70003825177, + name: 'BisleriGroup', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: false, }, - status: 200 + ], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', - method: 'GET' - }, - httpRes: { - data: { - "lists": [ - { - "id": 70000053624, - "name": "Sample list" - }, - { - "id": 70000056575, - "name": "list1-test" - }, - { - "id": 70000058627, - "name": "Jio 5G Group" - }, - { - "id": 70000058628, - "name": "Airtel 5G Group" - }, - { - "id": 70000059716, - "name": "Voda 5G" - } - ], - "meta": { - "total_pages": 1, - "total": 5 - } + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', + method: 'GET', + }, + httpRes: { + data: { + sales_activity_types: [ + { + partial: true, + id: 70000666879, + name: 'own-calender', + internal_name: 'cappointment', + show_in_conversation: true, + position: 1, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663932, + name: 'fb-support', + internal_name: 'facebook', + show_in_conversation: true, + position: 2, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663746, + name: 'twitter sales', + internal_name: 'twitter', + show_in_conversation: true, + position: 3, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000646396, + name: 'linked sales', + internal_name: 'linkedin', + show_in_conversation: true, + position: 4, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000642330, + name: 'facebook sales', + internal_name: 'facebook', + show_in_conversation: true, + position: 5, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000612897, + name: 'Chat', + internal_name: 'chat', + show_in_conversation: true, + position: 6, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612898, + name: 'Phone', + internal_name: 'phone', + show_in_conversation: true, + position: 7, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612899, + name: 'Meeting', + internal_name: 'appointment', + show_in_conversation: true, + position: 8, + is_default: true, + is_checkedin: true, + }, + { + partial: true, + id: 70000612900, + name: 'Task', + internal_name: 'task', + show_in_conversation: true, + position: 9, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612901, + name: 'Email', + internal_name: 'email', + show_in_conversation: true, + position: 10, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612902, + name: 'SMS Outgoing', + internal_name: 'sms_outgoing', + show_in_conversation: true, + position: 11, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612903, + name: 'Reminder', + internal_name: 'reminder', + show_in_conversation: false, + position: 12, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612904, + name: 'SMS Incoming', + internal_name: 'sms_incoming', + show_in_conversation: true, + position: 13, + is_default: true, + is_checkedin: false, + }, + ], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', + method: 'POST', + }, + httpRes: { + data: { + contact: { + id: 70054866612, + first_name: null, + last_name: null, + display_name: 'jamessampleton120@gmail.com', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'jamessampleton120@gmail.com', + emails: [ + { + id: 70047409219, + value: 'jamessampleton120@gmail.com', + is_primary: true, + label: null, + _destroy: false, }, - status: 200 + ], + time_zone: null, + work_number: null, + mobile_number: null, + address: null, + last_seen: null, + lead_score: 0, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70054866612/timeline_feeds', + document_associations: '/crm/sales/contacts/70054866612/document_associations', + notes: '/crm/sales/contacts/70054866612/notes?include=creater', + tasks: + '/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70054866612/duplicates', + connections: '/crm/sales/contacts/70054866612/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-10-11T08:42:15-04:00', + updated_at: '2022-10-11T08:42:15-04:00', + keyword: null, + medium: null, + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: null, + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: null, + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 0, + record_type_id: '71012139284', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', - method: 'GET' - }, - httpRes: { - data: { - "lifecycle_stages": [ - { - "id": 71012139274, - "name": "Sales Qualified Lead start", - "position": 1, - "disabled": false, - "default": true, - "type": "Sales Qualified Lead", - "contact_status_ids": [70000697858, 70000697859, 70000697860] - }, - { - "id": 71012139273, - "name": "Lead", - "position": 2, - "disabled": false, - "default": true, - "type": "Lead", - "contact_status_ids": [70000697854, 70000697855, 70000697856, 70000697857] - }, - { - "id": 71012806409, - "name": "final Customer", - "position": 3, - "disabled": false, - "default": false, - "type": "Custom", - "contact_status_ids": [70000736543, 70000736544] - }, - { - "id": 71012139275, - "name": "Customer", - "position": 4, - "disabled": false, - "default": true, - "type": "Customer", - "contact_status_ids": [70000697861, 70000697862] - } - ] - }, - status: 200 + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', + method: 'GET', + }, + httpRes: { + data: { + lists: [ + { + id: 70000053624, + name: 'Sample list', + }, + { + id: 70000056575, + name: 'list1-test', + }, + { + id: 70000058627, + name: 'Jio 5G Group', + }, + { + id: 70000058628, + name: 'Airtel 5G Group', + }, + { + id: 70000059716, + name: 'Voda 5G', + }, + ], + meta: { + total_pages: 1, + total: 5, }, - } + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', + method: 'GET', + }, + httpRes: { + data: { + lifecycle_stages: [ + { + id: 71012139274, + name: 'Sales Qualified Lead start', + position: 1, + disabled: false, + default: true, + type: 'Sales Qualified Lead', + contact_status_ids: [70000697858, 70000697859, 70000697860], + }, + { + id: 71012139273, + name: 'Lead', + position: 2, + disabled: false, + default: true, + type: 'Lead', + contact_status_ids: [70000697854, 70000697855, 70000697856, 70000697857], + }, + { + id: 71012806409, + name: 'final Customer', + position: 3, + disabled: false, + default: false, + type: 'Custom', + contact_status_ids: [70000736543, 70000736544], + }, + { + id: 71012139275, + name: 'Customer', + position: 4, + disabled: false, + default: true, + type: 'Customer', + contact_status_ids: [70000697861, 70000697862], + }, + ], + }, + status: 200, + }, + }, ]; - - - diff --git a/test/integrations/destinations/freshsales/network.ts b/test/integrations/destinations/freshsales/network.ts index f6043b265f..9d661f2686 100644 --- a/test/integrations/destinations/freshsales/network.ts +++ b/test/integrations/destinations/freshsales/network.ts @@ -1,484 +1,495 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "sales_account": { - "id": 70003771396, - "name": "postman2.0", - "address": "Red Colony", - "city": "Pune", - "state": "Goa", - "zipcode": null, - "country": null, - "number_of_employees": 11, - "annual_revenue": 1000, - "website": null, - "owner_id": null, - "phone": "919191919191", - "open_deals_amount": null, - "open_deals_count": null, - "won_deals_amount": null, - "won_deals_count": null, - "last_contacted": null, - "last_contacted_mode": null, - "facebook": null, - "twitter": null, - "linkedin": null, - "links": { - "conversations": "/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "document_associations": "/crm/sales/sales_accounts/70003771396/document_associations", - "notes": "/crm/sales/sales_accounts/70003771396/notes?include=creater", - "tasks": "/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note" - }, - "custom_field": {}, - "created_at": "2022-08-17T04:15:00-04:00", - "updated_at": "2022-08-24T06:03:31-04:00", - "avatar": null, - "parent_sales_account_id": null, - "recent_note": null, - "last_contacted_via_sales_activity": null, - "last_contacted_sales_activity_mode": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "last_assigned_at": null, - "tags": [], - "is_deleted": false, - "team_user_ids": null, - "has_connections": false, - "record_type_id": "71010794477" - } - }, - status: 200 - }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70042006456, - "first_name": "Rk", - "last_name": "Mishra", - "display_name": "Rk Mishra", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "testuser@google.com", - "emails": [ - { - "id": 70037311213, - "value": "testuser@google.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": "IST", - "work_number": "9988776655", - "mobile_number": "19265559504", - "address": null, - "last_seen": null, - "lead_score": 26, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70042006456/timeline_feeds", - "document_associations": "/crm/sales/contacts/70042006456/document_associations", - "notes": "/crm/sales/contacts/70042006456/notes?include=creater", - "tasks": "/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70042006456/duplicates", - "connections": "/crm/sales/contacts/70042006456/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-08-09T03:22:12-04:00", - "updated_at": "2022-08-30T00:33:27-04:00", - "keyword": "drilling", - "medium": "facebook", - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": "2022-08-29T05:51:24-04:00", - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": "ea5cfab2-3961-4d8a-8187-3d1858c99099", - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 2, - "record_type_id": "71010794476", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [], - "sales_accounts": [ - { - "partial": true, - "id": 70003771198, - "name": "div-quer", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": true - }, - { - "partial": true, - "id": 70003825177, - "name": "BisleriGroup", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": false - } - ] - } - }, - status: 200 + httpRes: { + data: { + sales_account: { + id: 70003771396, + name: 'postman2.0', + address: 'Red Colony', + city: 'Pune', + state: 'Goa', + zipcode: null, + country: null, + number_of_employees: 11, + annual_revenue: 1000, + website: null, + owner_id: null, + phone: '919191919191', + open_deals_amount: null, + open_deals_count: null, + won_deals_amount: null, + won_deals_count: null, + last_contacted: null, + last_contacted_mode: null, + facebook: null, + twitter: null, + linkedin: null, + links: { + conversations: + '/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + document_associations: '/crm/sales/sales_accounts/70003771396/document_associations', + notes: '/crm/sales/sales_accounts/70003771396/notes?include=creater', + tasks: + '/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + }, + custom_field: {}, + created_at: '2022-08-17T04:15:00-04:00', + updated_at: '2022-08-24T06:03:31-04:00', + avatar: null, + parent_sales_account_id: null, + recent_note: null, + last_contacted_via_sales_activity: null, + last_contacted_sales_activity_mode: null, + completed_sales_sequences: null, + active_sales_sequences: null, + last_assigned_at: null, + tags: [], + is_deleted: false, + team_user_ids: null, + has_connections: false, + record_type_id: '71010794477', }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', - method: 'GET' - }, - httpRes: { - data: { - "sales_activity_types": [ - { - "partial": true, - "id": 70000666879, - "name": "own-calender", - "internal_name": "cappointment", - "show_in_conversation": true, - "position": 1, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663932, - "name": "fb-support", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 2, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663746, - "name": "twitter sales", - "internal_name": "twitter", - "show_in_conversation": true, - "position": 3, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000646396, - "name": "linked sales", - "internal_name": "linkedin", - "show_in_conversation": true, - "position": 4, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000642330, - "name": "facebook sales", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 5, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612897, - "name": "Chat", - "internal_name": "chat", - "show_in_conversation": true, - "position": 6, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612898, - "name": "Phone", - "internal_name": "phone", - "show_in_conversation": true, - "position": 7, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612899, - "name": "Meeting", - "internal_name": "appointment", - "show_in_conversation": true, - "position": 8, - "is_default": true, - "is_checkedin": true - }, - { - "partial": true, - "id": 70000612900, - "name": "Task", - "internal_name": "task", - "show_in_conversation": true, - "position": 9, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612901, - "name": "Email", - "internal_name": "email", - "show_in_conversation": true, - "position": 10, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612902, - "name": "SMS Outgoing", - "internal_name": "sms_outgoing", - "show_in_conversation": true, - "position": 11, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612903, - "name": "Reminder", - "internal_name": "reminder", - "show_in_conversation": false, - "position": 12, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612904, - "name": "SMS Incoming", - "internal_name": "sms_incoming", - "show_in_conversation": true, - "position": 13, - "is_default": true, - "is_checkedin": false - } - ] - }, - status: 200 - }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70054866612, - "first_name": null, - "last_name": null, - "display_name": "jamessampleton120@gmail.com", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "jamessampleton120@gmail.com", - "emails": [ - { - "id": 70047409219, - "value": "jamessampleton120@gmail.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": null, - "work_number": null, - "mobile_number": null, - "address": null, - "last_seen": null, - "lead_score": 0, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70054866612/timeline_feeds", - "document_associations": "/crm/sales/contacts/70054866612/document_associations", - "notes": "/crm/sales/contacts/70054866612/notes?include=creater", - "tasks": "/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70054866612/duplicates", - "connections": "/crm/sales/contacts/70054866612/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-10-11T08:42:15-04:00", - "updated_at": "2022-10-11T08:42:15-04:00", - "keyword": null, - "medium": null, - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": null, - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": null, - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 0, - "record_type_id": "71012139284", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [] - } + httpRes: { + data: { + contact: { + id: 70042006456, + first_name: 'Rk', + last_name: 'Mishra', + display_name: 'Rk Mishra', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'testuser@google.com', + emails: [ + { + id: 70037311213, + value: 'testuser@google.com', + is_primary: true, + label: null, + _destroy: false, + }, + ], + time_zone: 'IST', + work_number: '9988776655', + mobile_number: '19265559504', + address: null, + last_seen: null, + lead_score: 26, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70042006456/timeline_feeds', + document_associations: '/crm/sales/contacts/70042006456/document_associations', + notes: '/crm/sales/contacts/70042006456/notes?include=creater', + tasks: + '/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70042006456/duplicates', + connections: '/crm/sales/contacts/70042006456/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-08-09T03:22:12-04:00', + updated_at: '2022-08-30T00:33:27-04:00', + keyword: 'drilling', + medium: 'facebook', + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: '2022-08-29T05:51:24-04:00', + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 2, + record_type_id: '71010794476', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], + sales_accounts: [ + { + partial: true, + id: 70003771198, + name: 'div-quer', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: true, + }, + { + partial: true, + id: 70003825177, + name: 'BisleriGroup', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: false, }, - status: 200 + ], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', - method: 'GET' - }, - httpRes: { - data: { - "lists": [ - { - "id": 70000053624, - "name": "Sample list" - }, - { - "id": 70000056575, - "name": "list1-test" - }, - { - "id": 70000058627, - "name": "Jio 5G Group" - }, - { - "id": 70000058628, - "name": "Airtel 5G Group" - }, - { - "id": 70000059716, - "name": "Voda 5G" - } - ], - "meta": { - "total_pages": 1, - "total": 5 - } + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', + method: 'GET', + }, + httpRes: { + data: { + sales_activity_types: [ + { + partial: true, + id: 70000666879, + name: 'own-calender', + internal_name: 'cappointment', + show_in_conversation: true, + position: 1, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663932, + name: 'fb-support', + internal_name: 'facebook', + show_in_conversation: true, + position: 2, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663746, + name: 'twitter sales', + internal_name: 'twitter', + show_in_conversation: true, + position: 3, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000646396, + name: 'linked sales', + internal_name: 'linkedin', + show_in_conversation: true, + position: 4, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000642330, + name: 'facebook sales', + internal_name: 'facebook', + show_in_conversation: true, + position: 5, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000612897, + name: 'Chat', + internal_name: 'chat', + show_in_conversation: true, + position: 6, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612898, + name: 'Phone', + internal_name: 'phone', + show_in_conversation: true, + position: 7, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612899, + name: 'Meeting', + internal_name: 'appointment', + show_in_conversation: true, + position: 8, + is_default: true, + is_checkedin: true, + }, + { + partial: true, + id: 70000612900, + name: 'Task', + internal_name: 'task', + show_in_conversation: true, + position: 9, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612901, + name: 'Email', + internal_name: 'email', + show_in_conversation: true, + position: 10, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612902, + name: 'SMS Outgoing', + internal_name: 'sms_outgoing', + show_in_conversation: true, + position: 11, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612903, + name: 'Reminder', + internal_name: 'reminder', + show_in_conversation: false, + position: 12, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612904, + name: 'SMS Incoming', + internal_name: 'sms_incoming', + show_in_conversation: true, + position: 13, + is_default: true, + is_checkedin: false, + }, + ], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', + method: 'POST', + }, + httpRes: { + data: { + contact: { + id: 70054866612, + first_name: null, + last_name: null, + display_name: 'jamessampleton120@gmail.com', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'jamessampleton120@gmail.com', + emails: [ + { + id: 70047409219, + value: 'jamessampleton120@gmail.com', + is_primary: true, + label: null, + _destroy: false, }, - status: 200 + ], + time_zone: null, + work_number: null, + mobile_number: null, + address: null, + last_seen: null, + lead_score: 0, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70054866612/timeline_feeds', + document_associations: '/crm/sales/contacts/70054866612/document_associations', + notes: '/crm/sales/contacts/70054866612/notes?include=creater', + tasks: + '/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70054866612/duplicates', + connections: '/crm/sales/contacts/70054866612/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-10-11T08:42:15-04:00', + updated_at: '2022-10-11T08:42:15-04:00', + keyword: null, + medium: null, + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: null, + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: null, + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 0, + record_type_id: '71012139284', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', - method: 'GET' - }, - httpRes: { - data: { - "lifecycle_stages": [ - { - "id": 71012139274, - "name": "Sales Qualified Lead start", - "position": 1, - "disabled": false, - "default": true, - "type": "Sales Qualified Lead", - "contact_status_ids": [70000697858, 70000697859, 70000697860] - }, - { - "id": 71012139273, - "name": "Lead", - "position": 2, - "disabled": false, - "default": true, - "type": "Lead", - "contact_status_ids": [70000697854, 70000697855, 70000697856, 70000697857] - }, - { - "id": 71012806409, - "name": "final Customer", - "position": 3, - "disabled": false, - "default": false, - "type": "Custom", - "contact_status_ids": [70000736543, 70000736544] - }, - { - "id": 71012139275, - "name": "Customer", - "position": 4, - "disabled": false, - "default": true, - "type": "Customer", - "contact_status_ids": [70000697861, 70000697862] - } - ] - }, - status: 200 + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', + method: 'GET', + }, + httpRes: { + data: { + lists: [ + { + id: 70000053624, + name: 'Sample list', + }, + { + id: 70000056575, + name: 'list1-test', + }, + { + id: 70000058627, + name: 'Jio 5G Group', + }, + { + id: 70000058628, + name: 'Airtel 5G Group', + }, + { + id: 70000059716, + name: 'Voda 5G', + }, + ], + meta: { + total_pages: 1, + total: 5, }, - } -]; \ No newline at end of file + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', + method: 'GET', + }, + httpRes: { + data: { + lifecycle_stages: [ + { + id: 71012139274, + name: 'Sales Qualified Lead start', + position: 1, + disabled: false, + default: true, + type: 'Sales Qualified Lead', + contact_status_ids: [70000697858, 70000697859, 70000697860], + }, + { + id: 71012139273, + name: 'Lead', + position: 2, + disabled: false, + default: true, + type: 'Lead', + contact_status_ids: [70000697854, 70000697855, 70000697856, 70000697857], + }, + { + id: 71012806409, + name: 'final Customer', + position: 3, + disabled: false, + default: false, + type: 'Custom', + contact_status_ids: [70000736543, 70000736544], + }, + { + id: 71012139275, + name: 'Customer', + position: 4, + disabled: false, + default: true, + type: 'Customer', + contact_status_ids: [70000697861, 70000697862], + }, + ], + }, + status: 200, + }, + }, +]; diff --git a/test/integrations/destinations/ga4/dataDelivery/business.ts b/test/integrations/destinations/ga4/dataDelivery/business.ts index 237da2fc3a..4b679c4cde 100644 --- a/test/integrations/destinations/ga4/dataDelivery/business.ts +++ b/test/integrations/destinations/ga4/dataDelivery/business.ts @@ -190,7 +190,7 @@ export const testScenariosForV1API = [ userId: 'dummyUserId', workspaceId: 'dummyWorkspaceId', }, - statusCode: 200, + statusCode: 400, }, ], statTags: { @@ -203,7 +203,7 @@ export const testScenariosForV1API = [ module: 'destination', workspaceId: 'dummyWorkspaceId', }, - status: 200, + status: 400, }, }, }, @@ -254,7 +254,7 @@ export const testScenariosForV1API = [ userId: 'dummyUserId', workspaceId: 'dummyWorkspaceId', }, - statusCode: 200, + statusCode: 400, }, ], statTags: { @@ -267,7 +267,7 @@ export const testScenariosForV1API = [ module: 'destination', workspaceId: 'dummyWorkspaceId', }, - status: 200, + status: 400, }, }, }, diff --git a/test/integrations/destinations/gainsight/network.ts b/test/integrations/destinations/gainsight/network.ts index c8adf871b9..4c5a026847 100644 --- a/test/integrations/destinations/gainsight/network.ts +++ b/test/integrations/destinations/gainsight/network.ts @@ -1,71 +1,71 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/query/Company', - method: 'POST', - }, - httpRes: { - data: { - "result": true, - "errorCode": null, - "errorDesc": null, - "requestId": "47d9c8be-4912-4610-806c-0eec22b73236", - "data": { - "records": [] - }, - "message": null - }, - status: 200 - }, + { + httpReq: { + url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/query/Company', + method: 'POST', }, - { - httpReq: { - url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/Company', - method: 'POST', + httpRes: { + data: { + result: true, + errorCode: null, + errorDesc: null, + requestId: '47d9c8be-4912-4610-806c-0eec22b73236', + data: { + records: [], }, - httpRes: { - data: { - "result": true, - "errorCode": null, - "errorDesc": null, - "requestId": "3ce46d4a-6a83-4a92-97b3-d9788a296af8", - "data": { - "count": 1, - "errors": null, - "records": [ - { - "Gsid": "1P0203VCESP7AUQMV9E953G" - } - ] - }, - "message": null + message: null, + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/Company', + method: 'POST', + }, + httpRes: { + data: { + result: true, + errorCode: null, + errorDesc: null, + requestId: '3ce46d4a-6a83-4a92-97b3-d9788a296af8', + data: { + count: 1, + errors: null, + records: [ + { + Gsid: '1P0203VCESP7AUQMV9E953G', }, - status: 200 + ], }, + message: null, + }, + status: 200, }, - { - httpReq: { - url: "https://demo-domain.gainsightcloud.com/v1/data/objects/Company?keys=Name", - method: 'GET', - }, - httpRes: { - data: { - "result": true, - "errorCode": null, - "errorDesc": null, - "requestId": "30630809-40a7-45d2-9673-ac2e80d06f33", - "data": { - "count": 1, - "errors": null, - "records": [ - { - "Gsid": "1P0203VCESP7AUQMV9E953G" - } - ] - }, - "message": null + }, + { + httpReq: { + url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/Company?keys=Name', + method: 'GET', + }, + httpRes: { + data: { + result: true, + errorCode: null, + errorDesc: null, + requestId: '30630809-40a7-45d2-9673-ac2e80d06f33', + data: { + count: 1, + errors: null, + records: [ + { + Gsid: '1P0203VCESP7AUQMV9E953G', }, - status: 200 + ], }, - } + message: null, + }, + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/gainsight_px/network.ts b/test/integrations/destinations/gainsight_px/network.ts index d9dd6bbaa0..81a2da4bed 100644 --- a/test/integrations/destinations/gainsight_px/network.ts +++ b/test/integrations/destinations/gainsight_px/network.ts @@ -1,222 +1,222 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/users/sample-user-id', - method: 'GET', - }, - httpRes: { - data: { - "aptrinsicId": "347c4c87-98c7-4ca6-a6da-678ed6924c22", - "identifyId": "sample-user-id", - "type": "USER", - "gender": "MALE", - "email": "user@email.com", - "firstName": "Sample", - "lastName": "User", - "lastSeenDate": 0, - "signUpDate": 1624431528295, - "firstVisitDate": 0, - "title": "engineer", - "phone": "", - "score": 0, - "role": "", - "subscriptionId": "", - "accountId": "", - "numberOfVisits": 1, - "location": { - "countryName": "USA", - "countryCode": "US", - "stateName": "", - "stateCode": "", - "city": "New York", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624431528295, - "lastModifiedDate": 1624431528295, - "customAttributes": null, - "globalUnsubscribe": false, - "sfdcContactId": "", - "lastVisitedUserAgentData": null, - "id": "sample-user-id", - "lastInferredLocation": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - } - }, - status: 200 - }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/sample-user-id', + method: 'GET', }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', - method: 'GET', + httpRes: { + data: { + aptrinsicId: '347c4c87-98c7-4ca6-a6da-678ed6924c22', + identifyId: 'sample-user-id', + type: 'USER', + gender: 'MALE', + email: 'user@email.com', + firstName: 'Sample', + lastName: 'User', + lastSeenDate: 0, + signUpDate: 1624431528295, + firstVisitDate: 0, + title: 'engineer', + phone: '', + score: 0, + role: '', + subscriptionId: '', + accountId: '', + numberOfVisits: 1, + location: { + countryName: 'USA', + countryCode: 'US', + stateName: '', + stateCode: '', + city: 'New York', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, - httpRes: { - data: { - "id": "ecorp-id", - "name": "ECorp", - "trackedSubscriptionId": "", - "sfdcId": "", - "lastSeenDate": 0, - "dunsNumber": "", - "industry": "software", - "numberOfEmployees": 400, - "sicCode": "", - "website": "www.ecorp.com", - "naicsCode": "", - "plan": "premium", - "location": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "numberOfUsers": 0, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624261864923, - "lastModifiedDate": 1624261864923, - "customAttributes": null, - "parentGroupId": "" - }, - status: 200 + propertyKeys: ['AP-XABC-123'], + createDate: 1624431528295, + lastModifiedDate: 1624431528295, + customAttributes: null, + globalUnsubscribe: false, + sfdcContactId: '', + lastVisitedUserAgentData: null, + id: 'sample-user-id', + lastInferredLocation: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, + }, + status: 200, }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', - method: 'PUT', - }, - httpRes: { - data: { - "id": "ecorp-id", - "name": "ECorp", - "trackedSubscriptionId": "", - "sfdcId": "", - "lastSeenDate": 0, - "dunsNumber": "", - "industry": "software", - "numberOfEmployees": 400, - "sicCode": "", - "website": "www.ecorp.com", - "naicsCode": "", - "plan": "premium", - "location": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "numberOfUsers": 0, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624261864923, - "lastModifiedDate": 1624261864923, - "customAttributes": null, - "parentGroupId": "" - }, - status: 204 - }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', + method: 'GET', }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/users/absent-id', - method: 'GET', + httpRes: { + data: { + id: 'ecorp-id', + name: 'ECorp', + trackedSubscriptionId: '', + sfdcId: '', + lastSeenDate: 0, + dunsNumber: '', + industry: 'software', + numberOfEmployees: 400, + sicCode: '', + website: 'www.ecorp.com', + naicsCode: '', + plan: 'premium', + location: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, - httpRes: { - data: { - externalapierror: { - status: "NOT_FOUND", - message: "User was not found for parameters {id=absent-id}", - debugMessage: null, - subErrors: null - } - }, - status: 404 + numberOfUsers: 0, + propertyKeys: ['AP-XABC-123'], + createDate: 1624261864923, + lastModifiedDate: 1624261864923, + customAttributes: null, + parentGroupId: '', + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', + method: 'PUT', + }, + httpRes: { + data: { + id: 'ecorp-id', + name: 'ECorp', + trackedSubscriptionId: '', + sfdcId: '', + lastSeenDate: 0, + dunsNumber: '', + industry: 'software', + numberOfEmployees: 400, + sicCode: '', + website: 'www.ecorp.com', + naicsCode: '', + plan: 'premium', + location: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, + numberOfUsers: 0, + propertyKeys: ['AP-XABC-123'], + createDate: 1624261864923, + lastModifiedDate: 1624261864923, + customAttributes: null, + parentGroupId: '', + }, + status: 204, + }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/absent-id', + method: 'GET', }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/users/stanley-kubrick', - method: 'GET', + httpRes: { + data: { + externalapierror: { + status: 'NOT_FOUND', + message: 'User was not found for parameters {id=absent-id}', + debugMessage: null, + subErrors: null, }, - httpRes: { - data: { - "id": "ecorp-id", - "name": "ECorp", - "trackedSubscriptionId": "", - "sfdcId": "", - "lastSeenDate": 0, - "dunsNumber": "", - "industry": "software", - "numberOfEmployees": 400, - "sicCode": "", - "website": "www.ecorp.com", - "naicsCode": "", - "plan": "premium", - "location": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "numberOfUsers": 0, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624261864923, - "lastModifiedDate": 1624261864923, - "customAttributes": null, - "parentGroupId": "" - }, - status: 200 + }, + status: 404, + }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/stanley-kubrick', + method: 'GET', + }, + httpRes: { + data: { + id: 'ecorp-id', + name: 'ECorp', + trackedSubscriptionId: '', + sfdcId: '', + lastSeenDate: 0, + dunsNumber: '', + industry: 'software', + numberOfEmployees: 400, + sicCode: '', + website: 'www.ecorp.com', + naicsCode: '', + plan: 'premium', + location: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, - } + numberOfUsers: 0, + propertyKeys: ['AP-XABC-123'], + createDate: 1624261864923, + lastModifiedDate: 1624261864923, + customAttributes: null, + parentGroupId: '', + }, + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts index 62ee03c46d..dff0f772d3 100644 --- a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts @@ -1,248 +1,347 @@ -export const data = [ +const events = [ + { + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 1, + userId: 'u1', + }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '1234567890', + subAccount: true, + loginCustomerId: '11', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + phone: '912382193', + firstName: 'John', + lastName: 'Gomes', + city: 'London', + state: 'UK', + streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { name: '', version: '' }, + screen: { density: 2 }, + }, + event: 'Page View', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + originalTimestamp: '2019-10-14T11:15:18.299Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + gclid: 'gclid1234', + conversionDateTime: '2022-01-01 12:32:45-08:00', + adjustedValue: '10', + currency: 'INR', + adjustmentDateTime: '2022-01-01 12:32:45-08:00', + partialFailure: true, + campaignId: '1', + templateId: '0', + order_id: 10000, + total: 1000, + products: [ + { + product_id: '507f1f77bcf86cd799439011', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + price: '19', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + quantity: '2', + }, + { + product_id: '507f1f77bcf86cd7994390112', + sku: '45790-322', + name: 'Monopoly: 3rd Edition2', + price: '192', + quantity: 22, + position: '12', + category: 'Cars2', + url: 'https://www.example.com/product/path2', + image_url: 'https://www.example.com/product/path.jpg2', + }, + ], + }, + integrations: { All: true }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + }, + { + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 2, + userId: 'u1', + }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '1234567890', + subAccount: true, + loginCustomerId: '', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + type: 'identify', + traits: { status: 'elizabeth' }, + userId: 'emrichardson820+22822@gmail.com', + channel: 'sources', + context: { + sources: { + job_id: '24c5HJxHomh6YCngEOCgjS5r1KX/Syncher', + task_id: 'vw_rs_mailchimp_mocked_hg_data', + version: 'v1.8.1', + batch_id: 'f252c69d-c40d-450e-bcd2-2cf26cb62762', + job_run_id: 'c8el40l6e87v0c4hkbl0', + task_run_id: 'c8el40l6e87v0c4hkblg', + }, + externalId: [ + { + id: 'emrichardson820+22822@gmail.com', + type: 'MAILCHIMP-92e1f1ad2c', + identifierType: 'email_address', + }, + ], + mappedToDestination: 'true', + }, + recordId: '1', + rudderId: '4d5d0ed0-9db8-41cc-9bb0-a032f6bfa97a', + messageId: 'b3bee036-fc26-4f6d-9867-c17f85708a82', + }, + }, + { + metadata: { secret: {}, jobId: 3, userId: 'u1' }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '1234567890', + subAccount: true, + loginCustomerId: '11', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + phone: '912382193', + firstName: 'John', + lastName: 'Gomes', + city: 'London', + state: 'UK', + streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { name: '', version: '' }, + screen: { density: 2 }, + }, + event: 'Page View', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + originalTimestamp: '2019-10-14T11:15:18.299Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + gclid: 'gclid1234', + conversionDateTime: '2022-01-01 12:32:45-08:00', + adjustedValue: '10', + currency: 'INR', + adjustmentDateTime: '2022-01-01 12:32:45-08:00', + partialFailure: true, + campaignId: '1', + templateId: '0', + order_id: 10000, + total: 1000, + products: [ + { + product_id: '507f1f77bcf86cd799439011', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + price: '19', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + quantity: '2', + }, + { + product_id: '507f1f77bcf86cd7994390112', + sku: '45790-322', + name: 'Monopoly: 3rd Edition2', + price: '192', + quantity: 22, + position: '12', + category: 'Cars2', + url: 'https://www.example.com/product/path2', + image_url: 'https://www.example.com/product/path.jpg2', + }, + ], + }, + integrations: { All: true }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + }, +]; + +const invalidRtTfCases = [ { name: 'google_adwords_enhanced_conversions', - description: 'Test 0', + description: 'Test 1 - should abort events, invalid router transform structure', feature: 'router', module: 'destination', version: 'v0', input: { request: { body: { - input: [ + input: events[0], + destType: 'google_adwords_enhanced_conversions', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ { - metadata: { - secret: { - access_token: 'abcd1234', - refresh_token: 'efgh5678', - developer_token: 'ijkl91011', - }, - jobId: 1, - userId: 'u1', - }, - destination: { - Config: { - rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: '1234567890', - subAccount: true, - loginCustomerId: '11', - listOfConversions: [ - { conversions: 'Page View' }, - { conversions: 'Product Added' }, - ], - authStatus: 'active', - }, - }, - message: { - channel: 'web', - context: { - app: { - build: '1.0.0', - name: 'RudderLabs JavaScript SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.0.0', - }, - traits: { - phone: '912382193', - firstName: 'John', - lastName: 'Gomes', - city: 'London', - state: 'UK', - streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', - }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - locale: 'en-US', - ip: '0.0.0.0', - os: { name: '', version: '' }, - screen: { density: 2 }, - }, - event: 'Page View', - type: 'track', - messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', - originalTimestamp: '2019-10-14T11:15:18.299Z', - anonymousId: '00000000000000000000000000', - userId: '12345', - properties: { - gclid: 'gclid1234', - conversionDateTime: '2022-01-01 12:32:45-08:00', - adjustedValue: '10', - currency: 'INR', - adjustmentDateTime: '2022-01-01 12:32:45-08:00', - partialFailure: true, - campaignId: '1', - templateId: '0', - order_id: 10000, - total: 1000, - products: [ - { - product_id: '507f1f77bcf86cd799439011', - sku: '45790-32', - name: 'Monopoly: 3rd Edition', - price: '19', - position: '1', - category: 'cars', - url: 'https://www.example.com/product/path', - image_url: 'https://www.example.com/product/path.jpg', - quantity: '2', - }, - { - product_id: '507f1f77bcf86cd7994390112', - sku: '45790-322', - name: 'Monopoly: 3rd Edition2', - price: '192', - quantity: 22, - position: '12', - category: 'Cars2', - url: 'https://www.example.com/product/path2', - image_url: 'https://www.example.com/product/path.jpg2', - }, - ], + error: 'Invalid event array', + metadata: [ + { + destType: 'google_adwords_enhanced_conversions', }, - integrations: { All: true }, - name: 'ApplicationLoaded', - sentAt: '2019-10-14T11:15:53.296Z', - }, + ], + batched: false, + statusCode: 400, }, + ], + }, + }, + }, + }, + { + name: 'google_adwords_enhanced_conversions', + description: + 'Test 2 - should abort events, invalid router transform structure without destType in payload & empty object as input', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: {}, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ { - metadata: { - secret: { - access_token: 'abcd1234', - refresh_token: 'efgh5678', - developer_token: 'ijkl91011', - }, - jobId: 2, - userId: 'u1', - }, - destination: { - Config: { - rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: '1234567890', - subAccount: true, - loginCustomerId: '', - listOfConversions: [ - { conversions: 'Page View' }, - { conversions: 'Product Added' }, - ], - authStatus: 'active', - }, - }, - message: { - type: 'identify', - traits: { status: 'elizabeth' }, - userId: 'emrichardson820+22822@gmail.com', - channel: 'sources', - context: { - sources: { - job_id: '24c5HJxHomh6YCngEOCgjS5r1KX/Syncher', - task_id: 'vw_rs_mailchimp_mocked_hg_data', - version: 'v1.8.1', - batch_id: 'f252c69d-c40d-450e-bcd2-2cf26cb62762', - job_run_id: 'c8el40l6e87v0c4hkbl0', - task_run_id: 'c8el40l6e87v0c4hkblg', - }, - externalId: [ - { - id: 'emrichardson820+22822@gmail.com', - type: 'MAILCHIMP-92e1f1ad2c', - identifierType: 'email_address', - }, - ], - mappedToDestination: 'true', + error: 'Invalid event array', + metadata: [ + { + destType: undefined, }, - recordId: '1', - rudderId: '4d5d0ed0-9db8-41cc-9bb0-a032f6bfa97a', - messageId: 'b3bee036-fc26-4f6d-9867-c17f85708a82', - }, + ], + batched: false, + statusCode: 400, }, + ], + }, + }, + }, + }, + { + name: 'google_adwords_enhanced_conversions', + description: + 'Test 3 - should abort events, invalid router transform structure without input & destType', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: {}, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ { - metadata: { secret: {}, jobId: 3, userId: 'u1' }, - destination: { - Config: { - rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: '1234567890', - subAccount: true, - loginCustomerId: '11', - listOfConversions: [ - { conversions: 'Page View' }, - { conversions: 'Product Added' }, - ], - authStatus: 'active', - }, - }, - message: { - channel: 'web', - context: { - app: { - build: '1.0.0', - name: 'RudderLabs JavaScript SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.0.0', - }, - traits: { - phone: '912382193', - firstName: 'John', - lastName: 'Gomes', - city: 'London', - state: 'UK', - streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', - }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - locale: 'en-US', - ip: '0.0.0.0', - os: { name: '', version: '' }, - screen: { density: 2 }, - }, - event: 'Page View', - type: 'track', - messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', - originalTimestamp: '2019-10-14T11:15:18.299Z', - anonymousId: '00000000000000000000000000', - userId: '12345', - properties: { - gclid: 'gclid1234', - conversionDateTime: '2022-01-01 12:32:45-08:00', - adjustedValue: '10', - currency: 'INR', - adjustmentDateTime: '2022-01-01 12:32:45-08:00', - partialFailure: true, - campaignId: '1', - templateId: '0', - order_id: 10000, - total: 1000, - products: [ - { - product_id: '507f1f77bcf86cd799439011', - sku: '45790-32', - name: 'Monopoly: 3rd Edition', - price: '19', - position: '1', - category: 'cars', - url: 'https://www.example.com/product/path', - image_url: 'https://www.example.com/product/path.jpg', - quantity: '2', - }, - { - product_id: '507f1f77bcf86cd7994390112', - sku: '45790-322', - name: 'Monopoly: 3rd Edition2', - price: '192', - quantity: 22, - position: '12', - category: 'Cars2', - url: 'https://www.example.com/product/path2', - image_url: 'https://www.example.com/product/path.jpg2', - }, - ], + error: 'Invalid event array', + metadata: [ + { + destType: undefined, }, - integrations: { All: true }, - name: 'ApplicationLoaded', - sentAt: '2019-10-14T11:15:53.296Z', - }, + ], + batched: false, + statusCode: 400, }, ], + }, + }, + }, + }, +]; + +export const data = [ + { + name: 'google_adwords_enhanced_conversions', + description: 'Test 0', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: events, destType: 'google_adwords_enhanced_conversions', }, method: 'POST', @@ -405,4 +504,5 @@ export const data = [ }, }, }, + ...invalidRtTfCases, ]; diff --git a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts index 1536af8187..a38980f0e9 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts @@ -96,6 +96,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, + jobId: 1, userId: 'u1', }, destination: { @@ -211,6 +212,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, + jobId: 2, userId: 'u1', }, destination: { @@ -274,7 +276,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 1, + jobId: 3, userId: 'u1', }, destination: { @@ -350,7 +352,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 2, + jobId: 4, userId: 'u1', }, destination: { @@ -426,7 +428,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 3, + jobId: 5, userId: 'u1', }, destination: { @@ -479,80 +481,130 @@ export const data = [ body: { output: [ { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://googleads.googleapis.com/v14/customers/7693729833/offlineUserDataJobs', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - }, - params: { event: 'Store sales', customerId: '7693729833' }, - body: { - JSON: { - event: '7693729833', - isStoreConversion: true, - createJobPayload: { - job: { - storeSalesMetadata: { - loyaltyFraction: 1, - transaction_upload_fraction: '1', + batchedRequest: [ + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://googleads.googleapis.com/v14/customers/7693729833/offlineUserDataJobs', + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + }, + params: { event: 'Store sales', customerId: '7693729833' }, + body: { + JSON: { + event: '7693729833', + isStoreConversion: true, + createJobPayload: { + job: { + storeSalesMetadata: { + loyaltyFraction: 1, + transaction_upload_fraction: '1', + }, + type: 'STORE_SALES_UPLOAD_FIRST_PARTY', }, - type: 'STORE_SALES_UPLOAD_FIRST_PARTY', }, - }, - addConversionPayload: { - operations: [ - { - create: { - transaction_attribute: { - store_attribute: { store_code: 'store code' }, - transaction_amount_micros: '100000000', - order_id: 'order id', - currency_code: 'INR', - transaction_date_time: '2019-10-14 16:45:18+05:30', - }, - userIdentifiers: [ - { - hashedEmail: - '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + addConversionPayload: { + operations: [ + { + create: { + transaction_attribute: { + store_attribute: { store_code: 'store code' }, + transaction_amount_micros: '100000000', + order_id: 'order id', + currency_code: 'INR', + transaction_date_time: '2019-10-14 16:45:18+05:30', }, - ], - }, - }, - { - create: { - transaction_attribute: { - store_attribute: { store_code: 'store code2' }, - transaction_amount_micros: '100000000', - order_id: 'order id', - currency_code: 'INR', - transaction_date_time: '2019-10-14 16:45:18+05:30', + userIdentifiers: [ + { + hashedEmail: + '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + }, + ], }, - userIdentifiers: [ - { - hashedEmail: - '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + }, + { + create: { + transaction_attribute: { + store_attribute: { store_code: 'store code2' }, + transaction_amount_micros: '100000000', + order_id: 'order id', + currency_code: 'INR', + transaction_date_time: '2019-10-14 16:45:18+05:30', }, - ], + userIdentifiers: [ + { + hashedEmail: + '6db61e6dcbcf2390e4a46af426f26a133a3bee45021422fc7ae86e9136f14110', + }, + ], + }, }, + ], + enable_partial_failure: false, + enable_warnings: false, + validate_only: true, + }, + executeJobPayload: { validate_only: true }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + }, + { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://googleads.googleapis.com/v14/customers/7693729833:uploadCallConversions', + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + }, + params: { + event: 'Order Completed', + customerId: '7693729833', + customVariables: [{ from: '', to: '' }], + properties: { + loyaltyFraction: 1, + order_id: 'order id', + currency: 'INR', + revenue: '100', + store_code: 'store code2', + email: 'alex@example.com', + gclid: 'gclid', + product_id: '123445', + quantity: 123, + callerId: '1234', + callStartDateTime: '2019-10-14T11:15:18.299Z', + }, + }, + body: { + JSON: { + conversions: [ + { + callerId: '1234', + callStartDateTime: '2019-10-14T11:15:18.299Z', + conversionDateTime: '2019-10-14 16:45:18+05:30', + conversionValue: 100, + currencyCode: 'INR', }, ], - enable_partial_failure: false, - enable_warnings: false, - validate_only: true, + partialFailure: true, }, - executeJobPayload: { validate_only: true }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, + files: {}, }, - files: {}, - }, + ], metadata: [ { secret: { @@ -560,7 +612,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 1, + jobId: 3, userId: 'u1', }, { @@ -569,7 +621,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 2, + jobId: 4, userId: 'u1', }, ], @@ -714,6 +766,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, + jobId: 1, userId: 'u1', }, ], @@ -824,6 +877,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, + jobId: 2, userId: 'u1', }, ], @@ -857,108 +911,6 @@ export const data = [ }, }, }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: - 'https://googleads.googleapis.com/v14/customers/7693729833:uploadCallConversions', - headers: { - Authorization: 'Bearer abcd1234', - 'Content-Type': 'application/json', - 'developer-token': 'ijkl91011', - }, - params: { - event: 'Order Completed', - customerId: '7693729833', - customVariables: [{ from: '', to: '' }], - properties: { - loyaltyFraction: 1, - order_id: 'order id', - currency: 'INR', - revenue: '100', - store_code: 'store code2', - email: 'alex@example.com', - gclid: 'gclid', - product_id: '123445', - quantity: 123, - callerId: '1234', - callStartDateTime: '2019-10-14T11:15:18.299Z', - }, - }, - body: { - JSON: { - conversions: [ - { - callerId: '1234', - callStartDateTime: '2019-10-14T11:15:18.299Z', - conversionDateTime: '2019-10-14 16:45:18+05:30', - conversionValue: 100, - currencyCode: 'INR', - }, - ], - partialFailure: true, - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - secret: { - access_token: 'abcd1234', - refresh_token: 'efgh5678', - developer_token: 'ijkl91011', - }, - jobId: 2, - userId: 'u1', - }, - ], - batched: false, - statusCode: 200, - destination: { - Config: { - rudderAccountId: '2Hsy2iFyoG5VLDd9wQcggHLMYFA', - customerId: '769-372-9833', - subAccount: false, - UserIdentifierSource: 'FIRST_PARTY', - conversionEnvironment: 'none', - defaultUserIdentifier: 'email', - hashUserIdentifier: true, - validateOnly: true, - eventsToOfflineConversionsTypeMapping: [ - { from: 'Data Reading Guide', to: 'click' }, - { from: 'Order Completed', to: 'store' }, - { from: 'Sign-up - click', to: 'click' }, - { from: 'Outbound click (rudderstack.com)', to: 'click' }, - { from: 'Page view', to: 'click' }, - { from: 'download', to: 'click' }, - { from: 'Product Clicked', to: 'store' }, - { from: 'Order Completed', to: 'call' }, - ], - loginCustomerId: '4219454086', - eventsToConversionsNamesMapping: [ - { from: 'Data Reading Guide', to: 'Data Reading Guide' }, - { from: 'Order Completed', to: 'Order Completed' }, - { from: 'Sign-up - click', to: 'Sign-up - click' }, - { - from: 'Outbound click (rudderstack.com)', - to: 'Outbound click (rudderstack.com)', - }, - { from: 'Page view', to: 'Page view' }, - { from: 'Sign up completed', to: 'Sign-up - click' }, - { from: 'download', to: 'Page view' }, - { from: 'Product Clicked', to: 'Store sales' }, - ], - authStatus: 'active', - oneTrustCookieCategories: [], - customVariables: [{ from: '', to: '' }], - }, - }, - }, { metadata: [ { @@ -967,7 +919,7 @@ export const data = [ refresh_token: 'efgh5678', developer_token: 'ijkl91011', }, - jobId: 3, + jobId: 5, userId: 'u1', }, ], diff --git a/test/integrations/destinations/iterable/deleteUsers/data.ts b/test/integrations/destinations/iterable/deleteUsers/data.ts new file mode 100644 index 0000000000..79d801f4ee --- /dev/null +++ b/test/integrations/destinations/iterable/deleteUsers/data.ts @@ -0,0 +1,186 @@ +const destType = 'iterable'; + +export const data = [ + { + name: destType, + description: 'Test 0: should fail when config is not being sent', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + ], + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'Config for deletion not present', + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 1: should fail when apiKey is not present in config', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder2', + }, + ], + config: { + apiToken: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'api key for deletion not present', + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 2: should fail when one of the user-deletion requests fails', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + userId: 'rudder2', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: + 'User deletion request failed for userIds : [{"userId":"rudder2","Reason":"User does not exist. Email: UserId: rudder2"}]', + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 3: should fail when invalid api key is set in config', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder3', + }, + { + userId: 'rudder4', + }, + ], + config: { + apiKey: 'invalidKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 401, + body: [ + { + error: 'User deletion request failed : Invalid API key', + statusCode: 401, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 4: should pass when proper apiKey & valid users are sent to destination', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder5', + }, + { + userId: 'rudder6', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 200, + status: 'successful', + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/iterable/network.ts b/test/integrations/destinations/iterable/network.ts new file mode 100644 index 0000000000..39544b2647 --- /dev/null +++ b/test/integrations/destinations/iterable/network.ts @@ -0,0 +1,109 @@ +const deleteNwData = [ + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder1', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder1 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder2', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'User does not exist. Email: UserId: rudder2', + code: 'BadParams', + params: null, + }, + status: 400, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder3', + headers: { + api_key: 'invalidKey', + }, + }, + httpRes: { + data: { + msg: 'Invalid API key', + code: 'Success', + params: { + endpoint: '/api/users/byUserId/rudder3', + }, + }, + status: 401, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder4', + headers: { + api_key: 'invalidKey', + }, + }, + httpRes: { + data: { + msg: 'Invalid API key', + code: 'Success', + params: { + endpoint: '/api/users/byUserId/rudder4', + }, + }, + status: 401, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder5', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder6 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder6', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder6 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, +]; +export const networkCallsData = [...deleteNwData]; diff --git a/test/integrations/destinations/klaviyo/network.ts b/test/integrations/destinations/klaviyo/network.ts index aa788a60da..d76d235c6f 100644 --- a/test/integrations/destinations/klaviyo/network.ts +++ b/test/integrations/destinations/klaviyo/network.ts @@ -1,75 +1,73 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://a.klaviyo.com/api/v2/list/XUepkK/subscribe', - method: 'GET', - }, - httpRes: { - status: 200 - }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/v2/list/XUepkK/subscribe', + method: 'GET', }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/v2/list/XUepkK/members', - method: 'GET', - }, - httpRes: { - status: 200 - }, + httpRes: { + status: 200, }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'GET', - data: { - attributes: { - email: "test3@rudderstack.com" - } - } - }, - httpRes: { - status: 409, - data: { - } - }, + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/v2/list/XUepkK/members', + method: 'GET', }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'GET', - }, - httpRes: { - status: 201, - data: { - data: { - id: '01GW3PHVY0MTCDGS0A1612HARX', - attributes: {} - }, - } - }, + httpRes: { + status: 200, }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'POST', - headers: { Authorization: 'Klaviyo-API-Key dummyPrivateApiKeyforfailure' } - }, - httpRes: { + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'GET', + data: { + attributes: { + email: 'test3@rudderstack.com', }, + }, + }, + httpRes: { + status: 409, + data: {}, }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'POST', + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'GET', + }, + httpRes: { + status: 201, + data: { + data: { + id: '01GW3PHVY0MTCDGS0A1612HARX', + attributes: {}, }, - httpRes: { - status: 201, - data: { - data: { - id: '01GW3PHVY0MTCDGS0A1612HARX', - attributes: {} - }, - } + }, + }, + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'POST', + headers: { Authorization: 'Klaviyo-API-Key dummyPrivateApiKeyforfailure' }, + }, + httpRes: {}, + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'POST', + }, + httpRes: { + status: 201, + data: { + data: { + id: '01GW3PHVY0MTCDGS0A1612HARX', + attributes: {}, }, - } + }, + }, + }, ]; diff --git a/test/integrations/destinations/rakuten/processor/transformationFailure.ts b/test/integrations/destinations/rakuten/processor/transformationFailure.ts index 906ddafd6a..e35ab26b69 100644 --- a/test/integrations/destinations/rakuten/processor/transformationFailure.ts +++ b/test/integrations/destinations/rakuten/processor/transformationFailure.ts @@ -46,7 +46,7 @@ export const transformationFailures = [ body: [ { error: - 'Missing required value from "properties.orderId": Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from "properties.orderId"', + 'Missing required value from ["properties.order_id","properties.orderId"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.order_id","properties.orderId"]', metadata: { destinationId: 'dummyDestId', jobId: '1', @@ -245,7 +245,7 @@ export const transformationFailures = [ body: [ { error: - 'Missing required value from ["properties.tr","properties.ranSiteID"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.tr","properties.ranSiteID"]', + 'Missing required value from ["properties.tr","properties.ran_site_id","properties.ranSiteID"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.tr","properties.ran_site_id","properties.ranSiteID"]', metadata: { destinationId: 'dummyDestId', jobId: '1', @@ -312,7 +312,7 @@ export const transformationFailures = [ body: [ { error: - 'Missing required value from ["properties.land","properties.landTime"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.land","properties.landTime"]', + 'Missing required value from ["properties.land","properties.land_time","properties.landTime"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.land","properties.land_time","properties.landTime"]', metadata: { destinationId: 'dummyDestId', jobId: '1', diff --git a/test/integrations/destinations/reddit/processor/data.ts b/test/integrations/destinations/reddit/processor/data.ts index f3cd4ebf7b..49e0cd2baa 100644 --- a/test/integrations/destinations/reddit/processor/data.ts +++ b/test/integrations/destinations/reddit/processor/data.ts @@ -166,6 +166,173 @@ export const data = [ }, }, }, + { + name: 'reddit', + description: 'Track call with order completed event with floating point values for revenue', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + context: { + traits: { + email: 'testone@gmail.com', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + ip: '54.100.200.255', + device: { + advertisingId: 'asfds7fdsihf734b34j43f', + }, + os: { + name: 'android', + }, + }, + type: 'track', + session_id: '16733896350494', + originalTimestamp: '2019-10-14T09:03:17.562Z', + anonymousId: '123456', + event: 'Order Completed', + userId: 'testuserId1', + properties: { + checkout_id: '12345', + order_id: '1234', + affiliation: 'Apple Store', + total: 20, + revenue: 14.985, + shipping: 4, + tax: 1, + discount: 1.5, + coupon: 'ImagePro', + currency: 'USD', + products: [ + { + product_id: '123', + sku: 'G-32', + name: 'Monopoly', + price: 14, + quantity: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + product_id: '345', + sku: 'F-32', + name: 'UNO', + price: 3.45, + quantity: 2, + category: 'Games', + }, + ], + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + destination: { + Config: { + accountId: 'a2_fsddXXXfsfd', + hashData: true, + eventsMapping: [ + { + from: 'Order Completed', + to: 'Purchase', + }, + ], + }, + DestinationDefinition: { Config: { cdkV2Enabled: true } }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + secret: { + accessToken: 'dummyAccessToken', + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_fsddXXXfsfd', + headers: { + Authorization: 'Bearer dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + events: [ + { + event_at: '2019-10-14T09:03:17.562Z', + event_type: { + tracking_type: 'Purchase', + }, + user: { + aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', + email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', + external_id: + '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', + ip_address: + 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', + user_agent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + screen_dimensions: {}, + }, + event_metadata: { + item_count: 2, + currency: 'USD', + value: 1499, + value_decimal: 14.99, + products: [ + { + id: '123', + name: 'Monopoly', + category: 'Games', + }, + { + id: '345', + name: 'UNO', + category: 'Games', + }, + ], + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + secret: { + accessToken: 'dummyAccessToken', + }, + }, + statusCode: 200, + }, + ], + }, + }, + }, { name: 'reddit', description: 'Track call with product list viewed event', diff --git a/test/integrations/destinations/the_trade_desk/common.ts b/test/integrations/destinations/the_trade_desk/common.ts index 8425d56431..28f46df829 100644 --- a/test/integrations/destinations/the_trade_desk/common.ts +++ b/test/integrations/destinations/the_trade_desk/common.ts @@ -9,14 +9,12 @@ const segmentName = 'test-segment'; const trackerId = 'test-trackerId'; const sampleDestination: Destination = { - Config: { advertiserId, advertiserSecretKey: 'test-advertiser-secret-key', dataServer: 'apac', ttlInDays: 30, audienceId: segmentName, - trackerId, }, DestinationDefinition: { Config: { cdkV2Enabled: true }, @@ -50,73 +48,12 @@ const sampleContext = { sources: sampleSource, }; -const sampleContextForConversion = { - app: { - build: '1.0.0', - name: 'RudderLabs Android SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.0.5', - }, - device: { - adTrackingEnabled: true, - advertisingId: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', - id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', - manufacturer: 'Google', - model: 'AOSP on IA Emulator', - name: 'generic_x86_arm', - type: 'ios', - attTrackingStatus: 3, - }, - externalId: [ - { - type: 'daid', - id: 'test-daid', - }, - ], - ip: '0.0.0.0', - page: { - referrer: 'https://docs.rudderstack.com/destinations/trade_desk', - }, - library: { - name: 'RudderLabs JavaScript SDK', - version: '1.0.5', - }, - locale: 'en-GB', - os: { - name: '', - version: '', - }, - screen: { - density: 2, - }, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36', -}; - -const integrationObject = { - All: true, - THE_TRADE_DESK: { - policies: ['LDU'], - region: 'US-CA', - privacy_settings: [ - { - privacy_type: 'GDPR', - is_applicable: 1, - consent_string: 'ok', - }, - ], - }, -}; - export { destType, destTypeInUpperCase, advertiserId, dataProviderId, segmentName, - trackerId, sampleDestination, sampleContext, - sampleContextForConversion, - integrationObject, }; diff --git a/test/integrations/destinations/the_trade_desk/delivery/data.ts b/test/integrations/destinations/the_trade_desk/delivery/data.ts index da8f60972e..320eb6dcfe 100644 --- a/test/integrations/destinations/the_trade_desk/delivery/data.ts +++ b/test/integrations/destinations/the_trade_desk/delivery/data.ts @@ -5,7 +5,6 @@ import { dataProviderId, segmentName, sampleDestination, - trackerId, } from '../common'; beforeAll(() => { @@ -246,169 +245,4 @@ export const data = [ }, }, }, - { - name: destType, - description: 'Successful delivery of realtime conversion event to Trade Desk', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - destinationResponse: { - response: { - Message: null, - EventResponses: [], - }, - status: 200, - }, - message: 'Request Processed Successfully', - status: 200, - }, - }, - }, - }, - }, - { - name: destType, - description: - 'Error response from the Trade Desk due to invalid real time conversion event payload', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - privacy_settings: [{}], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - destinationResponse: { - response: { - Message: null, - EventResponses: [ - { - EventIndex: 0, - EventErrors: [ - { - Error: 'InvalidPrivacySetting', - ErrorMessage: 'The request has an invalid privacy setting.', - }, - ], - EventWarnings: [], - Successful: false, - }, - ], - }, - status: 400, - }, - message: - 'Request failed with status: 400 due to {"Message":null,"EventResponses":[{"EventIndex":0,"EventErrors":[{"Error":"InvalidPrivacySetting","ErrorMessage":"The request has an invalid privacy setting."}],"EventWarnings":[],"Successful":false}]}', - statTags: { - destType: destTypeInUpperCase, - destinationId: 'Non-determininable', - errorCategory: 'network', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - status: 400, - }, - }, - }, - }, - }, ]; diff --git a/test/integrations/destinations/the_trade_desk/network.ts b/test/integrations/destinations/the_trade_desk/network.ts index 5908cbf8f5..ed6bdf4c7d 100644 --- a/test/integrations/destinations/the_trade_desk/network.ts +++ b/test/integrations/destinations/the_trade_desk/network.ts @@ -1,4 +1,4 @@ -import { destType, advertiserId, dataProviderId, segmentName, trackerId } from './common'; +import { destType, advertiserId, dataProviderId, segmentName } from './common'; export const networkCallsData = [ { @@ -103,107 +103,4 @@ export const networkCallsData = [ statusText: 'Ok', }, }, - { - httpReq: { - url: 'https://insight.adsrvr.org/track/realtimeconversion', - data: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - }, - ], - }, - params: { destination: destType }, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'POST', - }, - httpRes: { - data: { - Message: null, - EventResponses: [], - }, - status: 200, - statusText: 'OK', - }, - }, - { - httpReq: { - url: 'https://insight.adsrvr.org/track/realtimeconversion', - data: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - privacy_settings: [{}], - }, - ], - }, - params: { destination: destType }, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'RudderLabs', - }, - method: 'POST', - }, - httpRes: { - data: { - Message: null, - EventResponses: [ - { - EventIndex: 0, - EventErrors: [ - { - Error: 'InvalidPrivacySetting', - ErrorMessage: 'The request has an invalid privacy setting.', - }, - ], - EventWarnings: [], - Successful: false, - }, - ], - }, - status: 400, - statusText: 'Bad Request', - }, - }, ]; diff --git a/test/integrations/destinations/the_trade_desk/router/data.ts b/test/integrations/destinations/the_trade_desk/router/data.ts index 691ec703b9..f095f561db 100644 --- a/test/integrations/destinations/the_trade_desk/router/data.ts +++ b/test/integrations/destinations/the_trade_desk/router/data.ts @@ -6,11 +6,8 @@ import { advertiserId, dataProviderId, segmentName, - trackerId, sampleDestination, sampleContext, - sampleContextForConversion, - integrationObject, } from '../common'; export const data = [ @@ -868,786 +865,6 @@ export const data = [ }, mockFns: defaultMockFns, }, - { - name: destType, - description: - 'Mapped Ecommerce events (product added, product viewed, product added to wishlist, cart viewed, checkout started, order completed)', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'track', - event: 'Product Added', - messageId: 'messageId123', - context: sampleContextForConversion, - properties: { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - category: 'Games', - name: 'Cones of Dunshire', - brand: 'Wyatt Games', - variant: 'exapansion pack', - price: 49.99, - quantity: 5, - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - key1: 'value1', - }, - integrations: integrationObject, - }, - destination: overrideDestination(sampleDestination, { - customProperties: [ - { - rudderProperty: 'properties.key1', - tradeDeskProperty: 'td1', - }, - { - rudderProperty: 'properties.key2', - tradeDeskProperty: 'td2', - }, - ], - }), - metadata: { - jobId: 1, - }, - }, - { - message: { - type: 'track', - event: 'Product Viewed', - properties: { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - category: 'Games', - name: 'Cones of Dunshire', - brand: 'Wyatt Games', - variant: 'exapansion pack', - price: 49.99, - quantity: 5, - coupon: 'PREORDER15', - currency: 'USD', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - }, - }, - destination: sampleDestination, - metadata: { - jobId: 2, - }, - }, - { - message: { - type: 'track', - event: 'Product Added to Wishlist', - properties: { - wishlist_id: '74fkdjfl0jfdkdj29j030', - wishlist_name: 'New Games', - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - category: 'Games', - name: 'Cones of Dunshire', - brand: 'Wyatt Games', - variant: 'exapansion pack', - price: 49.99, - quantity: 1, - coupon: 'PREORDER15', - position: 1, - url: 'https://www.site.com/product/path', - image_url: 'https://www.site.com/product/path.jpg', - }, - }, - destination: sampleDestination, - metadata: { - jobId: 3, - }, - }, - { - message: { - type: 'track', - event: 'Cart Viewed', - properties: { - cart_id: '6b2c6f5aecf86a4ae77358ae3', - products: [ - { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - name: 'Cones of Dunshire', - price: 49.99, - position: 5, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - product_id: '577c6f5d5cf86a4c7735ba03', - sku: '3309-483-2201', - name: 'Five Crowns', - price: 5.99, - position: 2, - category: 'Games', - }, - ], - }, - }, - destination: sampleDestination, - metadata: { - jobId: 4, - }, - }, - { - message: { - type: 'track', - event: 'Checkout Started', - properties: { - order_id: '40684e8f0eaf000000000000', - affiliation: 'Vandelay Games', - value: 52, - revenue: 50.0, - shipping: 4, - tax: 3, - discount: 5, - coupon: 'NEWCUST5', - currency: 'USD', - products: [ - { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - name: 'Cones of Dunshire', - price: 40, - position: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - product_id: '577c6f5d5cf86a4c7735ba03', - sku: '3309-483-2201', - name: 'Five Crowns', - price: 5, - position: 2, - category: 'Games', - }, - ], - }, - }, - destination: sampleDestination, - metadata: { - jobId: 5, - }, - }, - { - message: { - type: 'track', - event: 'Order Completed', - properties: { - checkout_id: '70324a1f0eaf000000000000', - order_id: '40684e8f0eaf000000000000', - affiliation: 'Vandelay Games', - total: 52.0, - subtotal: 45.0, - revenue: 50.0, - shipping: 4.0, - tax: 3.0, - discount: 5.0, - coupon: 'NEWCUST5', - currency: 'USD', - products: [ - { - product_id: '622c6f5d5cf86a4c77358033', - sku: '8472-998-0112', - name: 'Cones of Dunshire', - price: 40, - position: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - product_id: '577c6f5d5cf86a4c7735ba03', - sku: '3309-483-2201', - name: 'Five Crowns', - price: 5, - position: 2, - category: 'Games', - }, - ], - }, - }, - destination: sampleDestination, - metadata: { - jobId: 6, - }, - }, - ], - destType, - }, - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'addtocart', - value: 249.95000000000002, - adid: 'test-daid', - adid_type: 'DAID', - client_ip: '0.0.0.0', - referrer_url: 'https://docs.rudderstack.com/destinations/trade_desk', - imp: 'messageId123', - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - td1: 'value1', - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - data_processing_option: { - policies: ['LDU'], - region: 'US-CA', - }, - privacy_settings: [ - { - privacy_type: 'GDPR', - is_applicable: 1, - consent_string: 'ok', - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 1, - }, - ], - batched: false, - statusCode: 200, - destination: overrideDestination(sampleDestination, { - customProperties: [ - { - rudderProperty: 'properties.key1', - tradeDeskProperty: 'td1', - }, - { - rudderProperty: 'properties.key2', - tradeDeskProperty: 'td2', - }, - ], - }), - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - event_name: 'viewitem', - value: 249.95000000000002, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 5, - price: 49.99, - }, - ], - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.webp', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 2, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'wishlistitem', - value: 49.99, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - qty: 1, - price: 49.99, - }, - ], - wishlist_id: '74fkdjfl0jfdkdj29j030', - wishlist_name: 'New Games', - category: 'Games', - brand: 'Wyatt Games', - variant: 'exapansion pack', - coupon: 'PREORDER15', - position: 1, - url: 'https://www.site.com/product/path', - image_url: 'https://www.site.com/product/path.jpg', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 3, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'viewcart', - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - price: 49.99, - position: 5, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - item_code: '577c6f5d5cf86a4c7735ba03', - name: 'Five Crowns', - price: 5.99, - position: 2, - category: 'Games', - }, - ], - cart_id: '6b2c6f5aecf86a4ae77358ae3', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 4, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - order_id: '40684e8f0eaf000000000000', - event_name: 'startcheckout', - value: 50, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - price: 40, - position: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - item_code: '577c6f5d5cf86a4c7735ba03', - name: 'Five Crowns', - price: 5, - position: 2, - category: 'Games', - }, - ], - affiliation: 'Vandelay Games', - shipping: 4, - tax: 3, - discount: 5, - coupon: 'NEWCUST5', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 5, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - currency: 'USD', - order_id: '40684e8f0eaf000000000000', - event_name: 'purchase', - value: 50, - items: [ - { - item_code: '622c6f5d5cf86a4c77358033', - name: 'Cones of Dunshire', - price: 40, - position: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - item_code: '577c6f5d5cf86a4c7735ba03', - name: 'Five Crowns', - price: 5, - position: 2, - category: 'Games', - }, - ], - checkout_id: '70324a1f0eaf000000000000', - affiliation: 'Vandelay Games', - total: 52.0, - subtotal: 45.0, - shipping: 4.0, - tax: 3.0, - discount: 5.0, - coupon: 'NEWCUST5', - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 6, - }, - ], - batched: false, - statusCode: 200, - destination: sampleDestination, - }, - ], - }, - }, - }, - }, - { - name: destType, - description: 'Custom event', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'track', - event: 'custom event abc', - properties: { - key1: 'value1', - value: 25, - product_id: 'prd123', - key2: true, - test: 'test123', - }, - }, - destination: overrideDestination(sampleDestination, { - customProperties: [ - { - rudderProperty: 'properties.key1', - tradeDeskProperty: 'td1', - }, - { - rudderProperty: 'properties.key2', - tradeDeskProperty: 'td2', - }, - ], - }), - metadata: { - jobId: 1, - }, - }, - ], - destType, - }, - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'custom event abc', - value: 25, - product_id: 'prd123', - test: 'test123', - td1: 'value1', - td2: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 1, - }, - ], - batched: false, - statusCode: 200, - destination: overrideDestination(sampleDestination, { - customProperties: [ - { - rudderProperty: 'properties.key1', - tradeDeskProperty: 'td1', - }, - { - rudderProperty: 'properties.key2', - tradeDeskProperty: 'td2', - }, - ], - }), - }, - ], - }, - }, - }, - }, - { - name: destType, - description: 'Mapped standard trade desk event', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'track', - event: 'custom event abc', - properties: { - key1: 'value1', - value: 25, - product_id: 'prd123', - key2: true, - test: 'test123', - }, - }, - destination: overrideDestination(sampleDestination, { - eventsMapping: [ - { - from: 'custom event abc', - to: 'direction', - }, - ], - }), - metadata: { - jobId: 1, - }, - }, - ], - destType, - }, - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'direction', - value: 25, - product_id: 'prd123', - test: 'test123', - key1: 'value1', - key2: true, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 1, - }, - ], - batched: false, - statusCode: 200, - destination: overrideDestination(sampleDestination, { - eventsMapping: [ - { - from: 'custom event abc', - to: 'direction', - }, - ], - }), - }, - ], - }, - }, - }, - }, { name: destType, description: 'Batch call with different event types', @@ -1674,30 +891,6 @@ export const data = [ jobId: 1, }, }, - { - message: { - type: 'track', - event: 'custom event abc', - properties: { - key1: 'value1', - value: 25, - revenue: 10, - product_id: 'prd123', - key2: true, - test: 'test123', - products: [ - { - product_id: 'prd123', - test: 'test', - }, - ], - }, - }, - destination: sampleDestination, - metadata: { - jobId: 2, - }, - }, { message: { type: 'identify', @@ -1711,7 +904,7 @@ export const data = [ }, destination: sampleDestination, metadata: { - jobId: 3, + jobId: 2, }, }, ], @@ -1767,53 +960,8 @@ export const data = [ destination: sampleDestination, }, { - batchedRequest: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', - headers: {}, - params: {}, - body: { - JSON: { - data: [ - { - tracker_id: trackerId, - adv: advertiserId, - event_name: 'custom event abc', - value: 25, - product_id: 'prd123', - test: 'test123', - key1: 'value1', - key2: true, - revenue: 10, - products: [ - { - product_id: 'prd123', - test: 'test', - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - metadata: [ - { - jobId: 2, - }, - ], batched: false, - statusCode: 200, - destination: sampleDestination, - }, - { - batched: false, - metadata: [{ jobId: 3 }], + metadata: [{ jobId: 2 }], statusCode: 400, error: 'Event type identify is not supported', statTags: { @@ -1831,119 +979,4 @@ export const data = [ }, }, }, - { - name: destType, - description: 'Tracker id is not present', - feature: 'router', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - input: [ - { - message: { - type: 'record', - action: 'insert', - fields: { - DAID: 'test-daid-1', - }, - channel: 'sources', - context: sampleContext, - recordId: '1', - }, - destination: overrideDestination(sampleDestination, { trackerId: '' }), - metadata: { - jobId: 1, - }, - }, - { - message: { - type: 'track', - event: 'custom event abc', - properties: { - key1: 'value1', - value: 25, - product_id: 'prd123', - key2: true, - test: 'test123', - }, - }, - destination: overrideDestination(sampleDestination, { trackerId: '' }), - metadata: { - jobId: 2, - }, - }, - ], - destType, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: [ - { - batchedRequest: [ - { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://sin-data.adsrvr.org/data/advertiser', - headers: {}, - params: {}, - body: { - JSON: { - DataProviderId: dataProviderId, - AdvertiserId: advertiserId, - Items: [ - { - DAID: 'test-daid-1', - Data: [ - { - Name: segmentName, - TTLInMinutes: 43200, - }, - ], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - }, - ], - metadata: [ - { - jobId: 1, - }, - ], - batched: true, - statusCode: 200, - destination: overrideDestination(sampleDestination, { trackerId: '' }), - }, - { - batched: false, - metadata: [{ jobId: 2 }], - statusCode: 400, - error: 'Tracking Tag ID is not present. Aborting', - statTags: { - errorCategory: 'dataValidation', - errorType: 'configuration', - destType: 'THE_TRADE_DESK', - module: 'destination', - implementation: 'cdkV2', - feature: 'router', - }, - destination: overrideDestination(sampleDestination, { trackerId: '' }), - }, - ], - }, - }, - }, - }, ]; 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 new file mode 100644 index 0000000000..9b79a7bcbd --- /dev/null +++ b/test/integrations/destinations/the_trade_desk_real_time_conversions/common.ts @@ -0,0 +1,91 @@ +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: Destination = { + Config: { + advertiserId, + trackerId, + }, + 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 = { + app: { + build: '1.0.0', + name: 'RudderLabs Android SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.5', + }, + device: { + adTrackingEnabled: true, + advertisingId: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a', + manufacturer: 'Google', + model: 'AOSP on IA Emulator', + name: 'generic_x86_arm', + type: 'ios', + attTrackingStatus: 3, + }, + externalId: [ + { + type: 'daid', + id: 'test-daid', + }, + ], + ip: '0.0.0.0', + page: { + referrer: 'https://docs.rudderstack.com/destinations/trade_desk', + }, + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.5', + }, + locale: 'en-GB', + os: { + name: '', + version: '', + }, + screen: { + density: 2, + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36', +}; + +const integrationObject = { + All: true, + THE_TRADE_DESK: { + policies: ['LDU'], + region: 'US-CA', + privacy_settings: [ + { + privacy_type: 'GDPR', + is_applicable: 1, + consent_string: 'ok', + }, + ], + }, +}; + +export { + destType, + destTypeInUpperCase, + advertiserId, + trackerId, + sampleDestination, + sampleContextForConversion, + integrationObject, +}; diff --git a/test/integrations/destinations/the_trade_desk_real_time_conversions/processor/data.ts b/test/integrations/destinations/the_trade_desk_real_time_conversions/processor/data.ts new file mode 100644 index 0000000000..264c760088 --- /dev/null +++ b/test/integrations/destinations/the_trade_desk_real_time_conversions/processor/data.ts @@ -0,0 +1,984 @@ +import { overrideDestination } from '../../../testUtils'; +import { + destType, + destTypeInUpperCase, + advertiserId, + trackerId, + sampleDestination, + sampleContextForConversion, + integrationObject, +} from '../common'; + +export const data = [ + { + name: destType, + description: 'Missing advertiser ID in the config', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'custom event abc', + properties: { + key1: 'value1', + value: 25, + product_id: 'prd123', + key2: true, + test: 'test123', + }, + }, + destination: overrideDestination(sampleDestination, { advertiserId: '' }), + metadata: { + jobId: 1, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Advertiser ID is not present. Aborting: Workflow: procWorkflow, Step: validateConfig, ChildStep: undefined, OriginalError: Advertiser ID is not present. Aborting', + statTags: { + destType: destTypeInUpperCase, + implementation: 'cdkV2', + feature: 'processor', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'configuration', + }, + metadata: { + jobId: 1, + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Tracker id is not present', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'custom event abc', + properties: { + key1: 'value1', + value: 25, + product_id: 'prd123', + key2: true, + test: 'test123', + }, + }, + destination: overrideDestination(sampleDestination, { trackerId: '' }), + metadata: { + jobId: 1, + }, + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Tracking Tag ID is not present. Aborting: Workflow: procWorkflow, Step: validateConfig, ChildStep: undefined, OriginalError: Tracking Tag ID is not present. Aborting', + statTags: { + destType: destTypeInUpperCase, + implementation: 'cdkV2', + feature: 'processor', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'configuration', + }, + metadata: { + jobId: 1, + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Unsupported event type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'identify', + context: { + traits: { + name: 'John Doe', + email: 'johndoe@gmail.com', + age: 25, + }, + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Event type identify is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: Event type identify is not supported', + statTags: { + destType: destTypeInUpperCase, + implementation: 'cdkV2', + feature: 'processor', + module: 'destination', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + }, + metadata: { + jobId: 1, + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Product Added', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Product Added', + messageId: 'messageId123', + context: sampleContextForConversion, + properties: { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + category: 'Games', + name: 'Cones of Dunshire', + brand: 'Wyatt Games', + variant: 'expansion pack', + price: 49.99, + quantity: 5, + coupon: 'PREORDER15', + position: 1, + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.webp', + key1: 'value1', + }, + integrations: integrationObject, + }, + destination: overrideDestination(sampleDestination, { + customProperties: [ + { + rudderProperty: 'properties.key1', + tradeDeskProperty: 'td1', + }, + { + rudderProperty: 'properties.key2', + tradeDeskProperty: 'td2', + }, + ], + }), + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'addtocart', + value: 249.95000000000002, + adid: 'test-daid', + adid_type: 'DAID', + client_ip: '0.0.0.0', + referrer_url: 'https://docs.rudderstack.com/destinations/trade_desk', + imp: 'messageId123', + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + qty: 5, + price: 49.99, + }, + ], + td1: 'value1', + category: 'Games', + brand: 'Wyatt Games', + variant: 'expansion pack', + coupon: 'PREORDER15', + position: 1, + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.webp', + data_processing_option: { + policies: ['LDU'], + region: 'US-CA', + }, + privacy_settings: [ + { + privacy_type: 'GDPR', + is_applicable: 1, + consent_string: 'ok', + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Product Viewed', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Product Viewed', + properties: { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + category: 'Games', + name: 'Cones of Dunshire', + brand: 'Wyatt Games', + variant: 'expansion pack', + price: 49.99, + quantity: 5, + coupon: 'PREORDER15', + currency: 'USD', + position: 1, + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.webp', + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + currency: 'USD', + event_name: 'viewitem', + value: 249.95000000000002, + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + qty: 5, + price: 49.99, + }, + ], + category: 'Games', + brand: 'Wyatt Games', + variant: 'expansion pack', + coupon: 'PREORDER15', + position: 1, + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.webp', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Product Added to Wishlist', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Product Added to Wishlist', + properties: { + wishlist_id: '74fkdjfl0jfdkdj29j030', + wishlist_name: 'New Games', + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + category: 'Games', + name: 'Cones of Dunshire', + brand: 'Wyatt Games', + variant: 'expansion pack', + price: 49.99, + quantity: 1, + coupon: 'PREORDER15', + position: 1, + url: 'https://www.site.com/product/path', + image_url: 'https://www.site.com/product/path.jpg', + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'wishlistitem', + value: 49.99, + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + qty: 1, + price: 49.99, + }, + ], + wishlist_id: '74fkdjfl0jfdkdj29j030', + wishlist_name: 'New Games', + category: 'Games', + brand: 'Wyatt Games', + variant: 'expansion pack', + coupon: 'PREORDER15', + position: 1, + url: 'https://www.site.com/product/path', + image_url: 'https://www.site.com/product/path.jpg', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Cart Viewed', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Cart Viewed', + properties: { + cart_id: '6b2c6f5aecf86a4ae77358ae3', + products: [ + { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + name: 'Cones of Dunshire', + price: 49.99, + position: 5, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + product_id: '577c6f5d5cf86a4c7735ba03', + sku: '3309-483-2201', + name: 'Five Crowns', + price: 5.99, + position: 2, + category: 'Games', + }, + ], + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'viewcart', + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + price: 49.99, + position: 5, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + item_code: '577c6f5d5cf86a4c7735ba03', + name: 'Five Crowns', + price: 5.99, + position: 2, + category: 'Games', + }, + ], + cart_id: '6b2c6f5aecf86a4ae77358ae3', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Checkout Started', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Checkout Started', + properties: { + order_id: '40684e8f0eaf000000000000', + affiliation: 'Vandelay Games', + value: 52, + revenue: 50.0, + shipping: 4, + tax: 3, + discount: 5, + coupon: 'NEWCUST5', + currency: 'USD', + products: [ + { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + name: 'Cones of Dunshire', + price: 40, + position: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + product_id: '577c6f5d5cf86a4c7735ba03', + sku: '3309-483-2201', + name: 'Five Crowns', + price: 5, + position: 2, + category: 'Games', + }, + ], + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + currency: 'USD', + order_id: '40684e8f0eaf000000000000', + event_name: 'startcheckout', + value: 50, + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + price: 40, + position: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + item_code: '577c6f5d5cf86a4c7735ba03', + name: 'Five Crowns', + price: 5, + position: 2, + category: 'Games', + }, + ], + affiliation: 'Vandelay Games', + shipping: 4, + tax: 3, + discount: 5, + coupon: 'NEWCUST5', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Order Completed', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'Order Completed', + properties: { + checkout_id: '70324a1f0eaf000000000000', + order_id: '40684e8f0eaf000000000000', + affiliation: 'Vandelay Games', + total: 52.0, + subtotal: 45.0, + revenue: 50.0, + shipping: 4.0, + tax: 3.0, + discount: 5.0, + coupon: 'NEWCUST5', + currency: 'USD', + products: [ + { + product_id: '622c6f5d5cf86a4c77358033', + sku: '8472-998-0112', + name: 'Cones of Dunshire', + price: 40, + position: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + product_id: '577c6f5d5cf86a4c7735ba03', + sku: '3309-483-2201', + name: 'Five Crowns', + price: 5, + position: 2, + category: 'Games', + }, + ], + }, + }, + destination: sampleDestination, + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + currency: 'USD', + order_id: '40684e8f0eaf000000000000', + event_name: 'purchase', + value: 50, + items: [ + { + item_code: '622c6f5d5cf86a4c77358033', + name: 'Cones of Dunshire', + price: 40, + position: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + item_code: '577c6f5d5cf86a4c7735ba03', + name: 'Five Crowns', + price: 5, + position: 2, + category: 'Games', + }, + ], + checkout_id: '70324a1f0eaf000000000000', + affiliation: 'Vandelay Games', + total: 52.0, + subtotal: 45.0, + shipping: 4.0, + tax: 3.0, + discount: 5.0, + coupon: 'NEWCUST5', + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Custom event', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'custom event abc', + properties: { + key1: 'value1', + value: 25, + product_id: 'prd123', + key2: true, + test: 'test123', + }, + }, + destination: overrideDestination(sampleDestination, { + customProperties: [ + { + rudderProperty: 'properties.key1', + tradeDeskProperty: 'td1', + }, + { + rudderProperty: 'properties.key2', + tradeDeskProperty: 'td2', + }, + ], + }), + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'custom event abc', + value: 25, + product_id: 'prd123', + test: 'test123', + td1: 'value1', + td2: true, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Mapped standard trade desk event', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'custom event abc', + properties: { + key1: 'value1', + value: 25, + product_id: 'prd123', + key2: true, + test: 'test123', + }, + }, + destination: overrideDestination(sampleDestination, { + eventsMapping: [ + { + from: 'custom event abc', + to: 'direction', + }, + ], + }), + metadata: { + jobId: 1, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://insight.adsrvr.org/track/realtimeconversion', + headers: { 'Content-Type': 'application/json' }, + params: {}, + body: { + JSON: { + data: [ + { + tracker_id: trackerId, + adv: advertiserId, + event_name: 'direction', + value: 25, + product_id: 'prd123', + test: 'test123', + key1: 'value1', + key2: true, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + jobId: 1, + }, + statusCode: 200, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/tiktok_ads/processor/data.ts b/test/integrations/destinations/tiktok_ads/processor/data.ts index 9d7c3a8d10..3b68426fbf 100644 --- a/test/integrations/destinations/tiktok_ads/processor/data.ts +++ b/test/integrations/destinations/tiktok_ads/processor/data.ts @@ -6870,4 +6870,107 @@ export const data = [ }, }, }, + { + name: 'tiktok_ads', + description: 'Test 46 -> V2 -> Event with no properties', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + anonymousId: '21e13f4bc7ceddad', + channel: 'web', + context: { + traits: { + email: 'dd6ff77f54e2106661089bae4d40cdb600979bf7edc9eb65c0942ba55c7c2d7f', + }, + userAgent: + 'Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion', + ip: '13.57.97.131', + locale: 'en-US', + externalId: [ + { + type: 'tiktokExternalId', + id: 'f0e388f53921a51f0bb0fc8a2944109ec188b59172935d8f23020b1614cc44bc', + }, + ], + }, + messageId: '84e26acc-56a5-4835-8233-591137fca468', + session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22', + originalTimestamp: '2019-10-14T09:03:17.562Z', + timestamp: '2020-09-17T19:49:27Z', + type: 'track', + event: 'customEvent', + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + destination: { + Config: { + version: 'v2', + accessToken: 'dummyAccessToken', + pixelCode: '{{PIXEL-CODE}}', + hashUserProperties: false, + sendCustomEvents: true, + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/event/track/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + event_source: 'web', + event_source_id: '{{PIXEL-CODE}}', + partner_name: 'RudderStack', + data: [ + { + event: 'customEvent', + event_id: '84e26acc-56a5-4835-8233-591137fca468', + event_time: 1600372167, + properties: { content_type: 'product' }, + user: { + locale: 'en-US', + email: 'dd6ff77f54e2106661089bae4d40cdb600979bf7edc9eb65c0942ba55c7c2d7f', + external_id: + 'f0e388f53921a51f0bb0fc8a2944109ec188b59172935d8f23020b1614cc44bc', + ip: '13.57.97.131', + user_agent: + 'Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion', + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts b/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts index 2b9341b851..81e125eaca 100644 --- a/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts +++ b/test/integrations/destinations/tiktok_ads_offline_events/processor/data.ts @@ -614,4 +614,248 @@ export const data = [ }, }, }, + { + name: 'tiktok_ads_offline_events', + description: 'Test 7 -> `search` standard tiktok Event through event mapping from UI', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + accessToken: 'dummyAccessToken', + hashUserProperties: true, + eventsToStandard: [ + { + from: 'custom event', + to: 'Search', + }, + ], + }, + }, + message: { + type: 'track', + event: 'custom event', + sentAt: '2023-03-22T00:02:33.802Z', + traits: { + email: [ + 'efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + phone: [ + 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + }, + userId: '60241286212', + channel: 'sources', + context: { + sources: { + job_id: '2N4WuoNQpGYmCPASUvnV86QyhY4/Syncher', + version: 'v1.20.0', + job_run_id: 'cgd4a063b2fn2e1j0q90', + task_run_id: 'cgd4a063b2fn2e1j0qa0', + }, + }, + recordId: '16322', + rudderId: '5b4ed73f-69aa-4198-88d1-3d4d509acbf1', + messageId: 'cgd4b663b2fn2e1j8th0', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + phone: 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + value: 32.839999999999996, + emails: + '["efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900","078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f","","","","","","","",""]', + eventId: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + currency: 'USD', + order_id: 60241286212, + eventSetId: '7211223771099742210', + event_name: 'CompletePayment', + }, + receivedAt: '2023-03-22T00:02:33.171Z', + request_ip: '10.7.78.187', + anonymousId: '60241286212', + originalTimestamp: '2023-03-22T00:02:33.802Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/offline/track/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + event_set_id: '7211223771099742210', + event_id: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + order_id: 60241286212, + currency: 'USD', + value: 32.839999999999996, + }, + event: 'Search', + partner_name: 'RudderStack', + context: { + user: { + emails: [ + '4dc75b075057df6f6b729e74a9feed1244dcf8ceb7903eaba13203f3268ae4b9', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + phone_numbers: [ + '28b7b205c2936d2ded022d2587fb2677a76e560e921b3ad615b739b0238baa5d', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + event_set_id: '7211223771099742210', + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'tiktok_ads_offline_events', + description: + 'Test 8 -> `PlaceAnOrder` standard tiktok Event through event `order completed` in payload', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: { + Config: { + accessToken: 'dummyAccessToken', + hashUserProperties: true, + eventsToStandard: [], + }, + }, + message: { + type: 'track', + event: 'order completed', + sentAt: '2023-03-22T00:02:33.802Z', + traits: { + email: [ + 'efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + phone: [ + 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + '078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f', + ], + }, + userId: '60241286212', + channel: 'sources', + context: { + sources: { + job_id: '2N4WuoNQpGYmCPASUvnV86QyhY4/Syncher', + version: 'v1.20.0', + job_run_id: 'cgd4a063b2fn2e1j0q90', + task_run_id: 'cgd4a063b2fn2e1j0qa0', + }, + }, + recordId: '16322', + rudderId: '5b4ed73f-69aa-4198-88d1-3d4d509acbf1', + messageId: 'cgd4b663b2fn2e1j8th0', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + phone: 'c4994d14e724936f1169147dddf1673a09af69b55cc54bc695dbe246bd093b05', + value: 32.839999999999996, + emails: + '["efaaf5c8803af4fbf305d7a110c832673d89ed40983770329092fd04b0ba7900","078d6c8e19f24093368d1712d7801970467f59216f7ccc087bf81b91e0e1f68f","","","","","","","",""]', + eventId: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + currency: 'USD', + order_id: 60241286212, + eventSetId: '7211223771099742210', + event_name: 'CompletePayment', + }, + receivedAt: '2023-03-22T00:02:33.171Z', + request_ip: '10.7.78.187', + anonymousId: '60241286212', + originalTimestamp: '2023-03-22T00:02:33.802Z', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://business-api.tiktok.com/open_api/v1.3/offline/track/', + headers: { + 'Access-Token': 'dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + event_set_id: '7211223771099742210', + event_id: '8965fb56-090f-47a5-aa7f-bbab22d9ec90', + timestamp: '2023-03-22T00:02:33.170Z', + properties: { + order_id: 60241286212, + currency: 'USD', + value: 32.839999999999996, + }, + event: 'PlaceAnOrder', + partner_name: 'RudderStack', + context: { + user: { + emails: [ + '4dc75b075057df6f6b729e74a9feed1244dcf8ceb7903eaba13203f3268ae4b9', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + phone_numbers: [ + '28b7b205c2936d2ded022d2587fb2677a76e560e921b3ad615b739b0238baa5d', + '77b639edeb3cd6c801ea05176b8acbfa38d5f38490b764cd0c80756d0cf9ec68', + ], + }, + }, + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + event_set_id: '7211223771099742210', + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/wootric/network.ts b/test/integrations/destinations/wootric/network.ts index 2407efa62b..1b51cc700c 100644 --- a/test/integrations/destinations/wootric/network.ts +++ b/test/integrations/destinations/wootric/network.ts @@ -1,183 +1,182 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/dummyId1?lookup_by_external_id=true', - method: 'GET', - }, - httpRes: { - status: 200, - data: { - "id": 486438462, - "created_at": "2022-08-10 11:39:50 -0700", - "updated_at": "2022-08-10 11:39:50 -0700", - "email": "dummyuser1@gmail.com", - "last_surveyed": "2022-01-20 05:39:21 -0800", - "external_created_at": 1611149961, - "last_seen_at": null, - "properties": { - "city": "Mumbai", - "name": "Dummy User 1", - "title": "SDE", - "gender": "Male", - "company": "Rudderstack" - }, - "phone_number": "+19123456789", - "external_id": "dummyId1", - "last_response": null, - "settings": { - "email_nps": true, - "mobile_nps": true, - "web_nps": true, - "force_mobile_survey": null, - "force_web_survey": null, - "surveys_disabled_by_end_user": null - } - }, - }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/dummyId1?lookup_by_external_id=true', + method: 'GET', }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/exclueFunTestId?lookup_by_external_id=true', - method: 'GET', - }, - httpRes: { - status: 200, - data: { - "id": 486336190, - "created_at": "2022-08-10 07:30:50 -0700", - "updated_at": "2022-08-10 10:12:46 -0700", - "email": "excludeUser@gmail.com", - "last_surveyed": "2022-01-20 05:39:21 -0800", - "external_created_at": 1579755367, - "last_seen_at": null, - "properties": { - "city": "Mumbai", - "name": "exclude test user", - "email": "excludeUser@gmail.com", - "title": "AD", - "gender": "Male", - "company": "Rockstar" - }, - "phone_number": "+18324671283", - "external_id": "exclueFunTestId", - "last_response": null, - "settings": { - "email_nps": true, - "mobile_nps": true, - "web_nps": true, - "force_mobile_survey": null, - "force_web_survey": null, - "surveys_disabled_by_end_user": null - } - }, - }, + httpRes: { + status: 200, + data: { + id: 486438462, + created_at: '2022-08-10 11:39:50 -0700', + updated_at: '2022-08-10 11:39:50 -0700', + email: 'dummyuser1@gmail.com', + last_surveyed: '2022-01-20 05:39:21 -0800', + external_created_at: 1611149961, + last_seen_at: null, + properties: { + city: 'Mumbai', + name: 'Dummy User 1', + title: 'SDE', + gender: 'Male', + company: 'Rudderstack', + }, + phone_number: '+19123456789', + external_id: 'dummyId1', + last_response: null, + settings: { + email_nps: true, + mobile_nps: true, + web_nps: true, + force_mobile_survey: null, + force_web_survey: null, + surveys_disabled_by_end_user: null, + }, + }, }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/my-external-id-1234?lookup_by_external_id=true', - method: 'POST', - - }, - httpRes: { - status: 200, - data: { - "type": "error_list", - "errors": [ - { - "status": "record_not_found", - "message": "The record could not be found", - "field": null - } - ] - } - }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/exclueFunTestId?lookup_by_external_id=true', + method: 'GET', }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/490635419', - method: 'GET' - }, - httpRes: { - data: { - "id": 490635419, - "created_at": "2022-08-20 00:55:26 -0700", - "updated_at": "2022-08-22 11:17:05 -0700", - "email": "firstuser@gmail.com", - "last_surveyed": "2022-08-01 00:11:44 -0700", - "external_created_at": 1661002761, - "last_seen_at": null, - "properties": { - "Department": "Marketing", - "product_plan": "Web", - "revenue amount": "5000" - }, - "phone_number": "+8859133456781", - "external_id": "firstUserId123", - "last_response": { - "id": 101013218, - "score": 9, - "text": "Good !!!", - "survey": { - "channel": "web" - } - }, - "settings": { - "email_nps": true, - "mobile_nps": true, - "web_nps": true, - "force_mobile_survey": null, - "force_web_survey": null, - "surveys_disabled_by_end_user": null - } - }, - status: 200, - }, + httpRes: { + status: 200, + data: { + id: 486336190, + created_at: '2022-08-10 07:30:50 -0700', + updated_at: '2022-08-10 10:12:46 -0700', + email: 'excludeUser@gmail.com', + last_surveyed: '2022-01-20 05:39:21 -0800', + external_created_at: 1579755367, + last_seen_at: null, + properties: { + city: 'Mumbai', + name: 'exclude test user', + email: 'excludeUser@gmail.com', + title: 'AD', + gender: 'Male', + company: 'Rockstar', + }, + phone_number: '+18324671283', + external_id: 'exclueFunTestId', + last_response: null, + settings: { + email_nps: true, + mobile_nps: true, + web_nps: true, + force_mobile_survey: null, + force_web_survey: null, + surveys_disabled_by_end_user: null, + }, + }, }, - { - httpReq: { - url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken', - method: 'POST' - }, - httpRes: { - data: { - "access_token": "2fe581c1c72851e73d60f4191f720be93e5d3e8a6147e37c4e8e852b1a8f506c", - "token_type": "Bearer", - "expires_in": 7200, - "refresh_token": "f4033a61742e84405a5ef8b2e09b82395dc041f0259fd5fb715fc196a1b9cd52", - "scope": "delete_account admin respond export read survey invalidate_response", - "created_at": 1660292389 - }, - status: 200, - }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/my-external-id-1234?lookup_by_external_id=true', + method: 'POST', }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/dummyId2?lookup_by_external_id=true', - method: 'GET' - }, - httpRes: { - status: 200, - }, + httpRes: { + status: 200, + data: { + type: 'error_list', + errors: [ + { + status: 'record_not_found', + message: 'The record could not be found', + field: null, + }, + ], + }, }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/12345', - method: 'GET' - }, - httpRes: { - status: 200, - }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/490635419', + method: 'GET', }, - { - httpReq: { - url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken12', - method: 'POST' - }, - httpRes: { - data: { - error: "Not found", - status: 404 - } - }, - } + httpRes: { + data: { + id: 490635419, + created_at: '2022-08-20 00:55:26 -0700', + updated_at: '2022-08-22 11:17:05 -0700', + email: 'firstuser@gmail.com', + last_surveyed: '2022-08-01 00:11:44 -0700', + external_created_at: 1661002761, + last_seen_at: null, + properties: { + Department: 'Marketing', + product_plan: 'Web', + 'revenue amount': '5000', + }, + phone_number: '+8859133456781', + external_id: 'firstUserId123', + last_response: { + id: 101013218, + score: 9, + text: 'Good !!!', + survey: { + channel: 'web', + }, + }, + settings: { + email_nps: true, + mobile_nps: true, + web_nps: true, + force_mobile_survey: null, + force_web_survey: null, + surveys_disabled_by_end_user: null, + }, + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken', + method: 'POST', + }, + httpRes: { + data: { + access_token: '2fe581c1c72851e73d60f4191f720be93e5d3e8a6147e37c4e8e852b1a8f506c', + token_type: 'Bearer', + expires_in: 7200, + refresh_token: 'f4033a61742e84405a5ef8b2e09b82395dc041f0259fd5fb715fc196a1b9cd52', + scope: 'delete_account admin respond export read survey invalidate_response', + created_at: 1660292389, + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/dummyId2?lookup_by_external_id=true', + method: 'GET', + }, + httpRes: { + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/12345', + method: 'GET', + }, + httpRes: { + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken12', + method: 'POST', + }, + httpRes: { + data: { + error: 'Not found', + status: 404, + }, + }, + }, ]; 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..2abe4c6d9a 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); @@ -143,7 +149,7 @@ export const generateSimplifiedIdentifyPayload: any = (parametersOverride: any) rudderId: parametersOverride.rudderId || generateAlphanumericId(36), messageId: parametersOverride.messageId || generateAlphanumericId(36), context: { - externalId: parametersOverride.externalId, + externalId: parametersOverride.context.externalId, traits: parametersOverride.context.traits, }, anonymousId: parametersOverride.anonymousId || 'default-anonymousId', @@ -198,7 +204,7 @@ export const generateSimplifiedTrackPayload: any = (parametersOverride: any) => rudderId: parametersOverride.rudderId || generateAlphanumericId(36), messageId: parametersOverride.messageId || generateAlphanumericId(36), context: removeUndefinedAndNullValues({ - externalId: parametersOverride.externalId, + externalId: parametersOverride.context.externalId, traits: parametersOverride.context.traits, }), anonymousId: parametersOverride.anonymousId || 'default-anonymousId', @@ -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; }; // ----------------------------- @@ -478,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; @@ -515,10 +521,15 @@ 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: { + accessToken: 'default-accessToken', + }, + dontBatch: false, }; }; diff --git a/tsconfig.json b/tsconfig.json index 9db40dd0e1..926831b612 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ /* Language and Environment */ "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "lib": [ - "es2019" + "es2019", ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ @@ -100,8 +100,8 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */, }, "exclude": ["./src/**/*.test.js", "./src/**/*.test.ts", "./test"], - "include": ["./src", "./src/**/*.json"] + "include": ["./src", "./src/**/*.json"], }