Skip to content

Commit

Permalink
chore: add router test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
anantjain45823 committed Aug 30, 2024
1 parent 6744cb6 commit 9ccebdf
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 104 deletions.
2 changes: 1 addition & 1 deletion src/v0/destinations/x_audience/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const BASE_URL =
'https://ads-api.twitter.com/12/accounts/:account_id/custom_audiences/:custom_audience_id/users';
'https://ads-api.twitter.com/12/accounts/:account_id/custom_audiences/:audience_id/users';
const MAX_PAYLOAD_SIZE_IN_BYTES = 4000000;
const MAX_OPERATIONS = 2500;
module.exports = { BASE_URL, MAX_PAYLOAD_SIZE_IN_BYTES, MAX_OPERATIONS };
49 changes: 8 additions & 41 deletions src/v0/destinations/x_audience/transform.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,21 @@
/* eslint-disable @typescript-eslint/naming-convention */
const {
removeUndefinedAndNullAndEmptyValues,
handleRtTfSingleEventError,
InstrumentationError,
} = require('@rudderstack/integrations-lib');
const { getAuthHeaderForRequest } = require('../twitter_ads/util');
const { defaultRequestConfig, defaultPostRequestConfig } = require('../../util');
const { BASE_URL } = require('./config');
const { JSON_MIME_TYPE } = require('../../util/constant');
const { getOAuthFields, batchEvents, getUserDetails } = require('./utils');

// Docs: https://developer.x.com/en/docs/x-ads-api/audiences/api-reference/custom-audience-user
const buildResponseWithJSON = (JSON, config, metadata) => {
const response = defaultRequestConfig();
response.endpoint = BASE_URL.replace(':account_id', config.accountId).replace(
':custom_audience_id',
config.audienceId,
);
response.method = defaultPostRequestConfig.requestMethod;
response.body.JSON = JSON;
// required to be in accordance with oauth package
const request = {
url: response.endpoint,
method: response.method,
body: response.body.JSON,
};

const oAuthObject = getOAuthFields(metadata);
const authHeader = getAuthHeaderForRequest(request, oAuthObject).Authorization;
response.headers = {
Authorization: authHeader,
'Content-Type': JSON_MIME_TYPE,
};
return response;
};
const { handleRtTfSingleEventError } = require('../../util');
const { batchEvents, buildResponseWithJSON, getUserDetails } = require('./utils');
/**
* This function returns audience object in the form of destination API
* @param {*} message
* @param {*} destination
* @param {*} metadata
*/
const processRecordEvent = (message, config) => {
const { fields, action } = message;
const { fields, action, type } = message;
if (type !== 'record') {
throw new InstrumentationError(`[X AUDIENCE]: ${type} is not supported`);
}
const { effective_at, expires_at } = fields;
const users = [getUserDetails(fields, config)];

Expand All @@ -57,10 +31,6 @@ const processRecordEvent = (message, config) => {
const process = (event) => {
const { message, destination, metadata } = event;
const { config } = destination;

if (message.type !== 'record') {
throw new InstrumentationError(`[X AUDIENCE]: ${message.type} is not supported`);
}
const payload = [processRecordEvent(message, config)];
return buildResponseWithJSON(payload, config, metadata);
};
Expand All @@ -86,12 +56,9 @@ const processRouterDest = async (inputs, reqMetadata) => {
errorRespList.push(errRespEvent);
}
});
const batchedResponseList = [];
let batchedResponseList = [];
if (responseList.length > 0) {
const batchedEvents = batchEvents(responseList, destination);
batchedEvents.forEach((batch) => {
batchedResponseList.push(buildResponseWithJSON(batch));
});
batchedResponseList = batchEvents(responseList, destination);
}
return [...batchedResponseList, ...errorRespList];
};
Expand Down
67 changes: 49 additions & 18 deletions src/v0/destinations/x_audience/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ const sha256 = require('sha256');
const lodash = require('lodash');
const jsonSize = require('json-size');
const { OAuthSecretError } = require('@rudderstack/integrations-lib');
const { getSuccessRespEvents, removeUndefinedAndNullAndEmptyValues } = require('../../util');
const { MAX_PAYLOAD_SIZE_IN_BYTES, MAX_OPERATIONS } = require('./config');
const { buildResponseWithJSON } = require('./transform');
const {
defaultRequestConfig,
defaultPostRequestConfig,
getSuccessRespEvents,
removeUndefinedAndNullAndEmptyValues,
} = require('../../util');
const { MAX_PAYLOAD_SIZE_IN_BYTES, BASE_URL, MAX_OPERATIONS } = require('./config');
const { getAuthHeaderForRequest } = require('../twitter_ads/util');
const { JSON_MIME_TYPE } = require('../../util/constant');

const getOAuthFields = ({ secret }) => {
if (!secret) {
throw new OAuthSecretError('[TWITTER ADS]:: OAuth - access keys not found');
throw new OAuthSecretError('[X Audience]:: OAuth - access keys not found');

Check warning on line 18 in src/v0/destinations/x_audience/utils.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/x_audience/utils.js#L18

Added line #L18 was not covered by tests
}
const oAuthObject = {
consumerKey: secret.consumerKey,
Expand All @@ -20,6 +26,31 @@ const getOAuthFields = ({ secret }) => {
return oAuthObject;
};

// Docs: https://developer.x.com/en/docs/x-ads-api/audiences/api-reference/custom-audience-user
const buildResponseWithJSON = (JSON, config, metadata) => {
const response = defaultRequestConfig();
response.endpoint = BASE_URL.replace(':account_id', config.accountId).replace(
':audience_id',
config.audienceId,
);
response.method = defaultPostRequestConfig.requestMethod;
response.body.JSON = JSON;
// required to be in accordance with oauth package
const request = {
url: response.endpoint,
method: response.method,
body: response.body.JSON,
};

const oAuthObject = getOAuthFields(metadata);
const authHeader = getAuthHeaderForRequest(request, oAuthObject).Authorization;
response.headers = {
Authorization: authHeader,
'Content-Type': JSON_MIME_TYPE,
};
return response;
};

/**
* This fucntion groups the response list based upoin 3 fields that are
* 1. operation_type
Expand Down Expand Up @@ -50,7 +81,7 @@ const getFinalResponseList = (operationObjectList, destination) => {
const { payload, metadataList } = operationObject;
metadataWithSecret = { secret: metadataList[0].secret };
if (
currentBatchedRequest.length > MAX_OPERATIONS ||
currentBatchedRequest.length >= MAX_OPERATIONS ||
jsonSize([...currentBatchedRequest, payload]) > MAX_PAYLOAD_SIZE_IN_BYTES
) {
respList.push(
Expand All @@ -61,7 +92,7 @@ const getFinalResponseList = (operationObjectList, destination) => {
true,
),
);
currentBatchedRequest = [operationObject];
currentBatchedRequest = [payload];
currentMetadataList = metadataList;
} else {
currentBatchedRequest.push(payload);
Expand Down Expand Up @@ -91,41 +122,41 @@ const getFinalResponseList = (operationObjectList, destination) => {
const getOperationObjectList = (eventGroups) => {
const operationList = [];
Object.keys(eventGroups).forEach((group) => {
const { operation, params } = group[0].message;
const { operation_type, params } = eventGroups[group][0].message;
const { effective_at, expires_at } = params;
let currentUserList = [];
let currentMetadata = [];
group.forEach((event) => {
eventGroups[group].forEach((event) => {
const newUsers = event.message.params.users;
// calculating size before appending the user and metadata list
if (jsonSize([...currentUserList, ...newUsers]).length > MAX_PAYLOAD_SIZE_IN_BYTES) {
if (jsonSize([...currentUserList, ...newUsers]) < MAX_PAYLOAD_SIZE_IN_BYTES) {
currentUserList.push(...event.message.params.users);
currentMetadata.push(event.metadata);
} else {
operationList.push({

Check warning on line 136 in src/v0/destinations/x_audience/utils.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/x_audience/utils.js#L135-L136

Added lines #L135 - L136 were not covered by tests
payload: {
operation_type: operation,
params: {
operation_type,
params: removeUndefinedAndNullAndEmptyValues({
effective_at,
expires_at,
users: currentUserList,
},
}),
},
metadataList: currentMetadata,
});
currentUserList = event.message.params.users;
currentMetadata = event.metadata;

Check warning on line 148 in src/v0/destinations/x_audience/utils.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/x_audience/utils.js#L147-L148

Added lines #L147 - L148 were not covered by tests
}
currentUserList = event.message.params.users;
currentMetadata = event.metadata;
});
// all the remaining user and metadata list used in one list
operationList.push({
payload: {
operation_type: operation,
params: {
operation_type,
params: removeUndefinedAndNullAndEmptyValues({
effective_at,
expires_at,
users: currentUserList,
},
}),
},
metadataList: currentMetadata,
});
Expand Down Expand Up @@ -200,4 +231,4 @@ const getUserDetails = (fields, config) => {
}
return removeUndefinedAndNullAndEmptyValues(user);
};
module.exports = { getOAuthFields, batchEvents, getUserDetails };
module.exports = { getOAuthFields, batchEvents, getUserDetails, buildResponseWithJSON };
28 changes: 28 additions & 0 deletions test/integrations/destinations/x_audience/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const authHeaderConstant =
'"OAuth oauth_consumer_key="validConsumerKey", oauth_nonce="j8kZvaJQRTaLX8h460CgHNs6rCEArNOW", oauth_signature="uAu%2FGdA6qPGW88pjVd7%2FgnAlHtM%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1725014809", oauth_token="validAccessToken", oauth_version="1.0"';

export const destination = {
config: {
accountId: '1234',
audienceId: 'dummyId',
},
ID: 'xpixel-1234',
};

export const generateMetadata = (jobId: number, userId?: string): any => {
return {
jobId,
attemptNum: 1,
userId: userId || 'default-userId',
sourceId: 'default-sourceId',
destinationId: 'default-destinationId',
workspaceId: 'default-workspaceId',
secret: {
consumerKey: 'validConsumerKey',
consumerSecret: 'validConsumerSecret',
accessToken: 'validAccessToken',
accessTokenSecret: 'validAccessTokenSecret',
},
dontBatch: false,
};
};
55 changes: 11 additions & 44 deletions test/integrations/destinations/x_audience/processor/data.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,13 @@
import { getOAuthFields, batchEvents } from '../../../../../src/v0/destinations/x_audience/utils';
import { destination, authHeaderConstant, generateMetadata } from '../common';

const authHeaderConstant =
'"OAuth oauth_consumer_key="validConsumerKey", oauth_nonce="j8kZvaJQRTaLX8h460CgHNs6rCEArNOW", oauth_signature="uAu%2FGdA6qPGW88pjVd7%2FgnAlHtM%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1725014809", oauth_token="validAccessToken", oauth_version="1.0"';

const destination = {
config: {
username: '[email protected]',
password: 'password@123',
accountToken: 'NPS-dummyToken12',
accountId: '1234',
audienceId: 'dummyId',
},
ID: 'wootric-1234',
};
const generateMetadata = (jobId: number, userId?: string): any => {
return {
jobId,
attemptNum: 1,
userId: userId || 'default-userId',
sourceId: 'default-sourceId',
destinationId: 'default-destinationId',
workspaceId: 'default-workspaceId',
secret: {
consumerKey: 'validConsumerKey',
consumerSecret: 'validConsumerSecret',
accessToken: 'validAccessToken',
accessTokenSecret: 'validAccessTokenSecret',
},
dontBatch: false,
};
const fields = {
email: '[email protected],[email protected]',
phone_number: '98765433232,21323',
handle: '@abc,@xyz',
twitter_id: 'tid1,tid2',
partner_user_id: 'puid1,puid2',
};

export const data = [
{
name: 'x_audience',
Expand All @@ -47,16 +25,11 @@ export const data = [
type: 'record',
action: 'insert',
fields: {
email: '[email protected],[email protected]',
phone_number: '98765433232,21323',
handle: '@abc,@xyz',
...fields,
device_id: 'did123,did456',
twitter_id: 'tid1,tid2',
partner_user_id: 'puid1,puid2',
effective_at: '2024-05-15T00:00:00Z',
expires_at: '2025-05-15T00:00:00Z',
},
channel: 'sources',
context: {},
recordId: '1',
},
Expand Down Expand Up @@ -145,13 +118,7 @@ export const data = [
message: {
type: 'record',
action: 'delete',
fields: {
email: '[email protected],[email protected]',
phone_number: '98765433232,21323',
handle: '@abc,@xyz',
twitter_id: 'tid1,tid2',
partner_user_id: 'puid1,puid2',
},
fields,
channel: 'sources',
context: {},
recordId: '1',
Expand Down Expand Up @@ -219,7 +186,7 @@ export const data = [
request: {
body: [
{
destination: { ...destination, config: { ...destination.config, enableHash: true } },
destination,
message: {
type: 'identify',
context: {},
Expand Down
Loading

0 comments on commit 9ccebdf

Please sign in to comment.