diff --git a/src/cdk/v2/destinations/rakuten/config.js b/src/cdk/v2/destinations/rakuten/config.js
new file mode 100644
index 00000000000..6a4c93ea3ab
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/config.js
@@ -0,0 +1,34 @@
+const { getMappingConfig } = require('../../../../v0/util');
+
+const ConfigCategories = {
+ TRACK: {
+ type: 'track',
+ name: 'propertiesMapping',
+ },
+};
+const mappingConfig = getMappingConfig(ConfigCategories, __dirname);
+// Following contains the keys at item level mapping where key can be considered as destkey and value can be considered as sourcekey
+const productProperties = {
+ skulist: 'sku',
+ qlist: 'quantity',
+ namelist: 'name',
+ brandlist: 'brand',
+ couponlist: 'coupon',
+ catidlist: 'categoryId',
+ catlist: 'category',
+ disamtlist: 'discountAmount',
+ distypelist: 'discountType',
+ isclearancelist: 'isClearance',
+ ismarketplacelist: 'isMarketPlace',
+ issalelist: 'isSale',
+ itmstatuslist: 'itmStatus',
+ marginlist: 'margin',
+ markdownlist: 'markdown',
+ shipidlist: 'shipId',
+ shipbylist: 'shipBy',
+ taxexemptlist: 'taxExempt',
+ sequencelist: 'sequence',
+};
+// list of all properties that are required
+const requiredProductProperties = ['skulist', 'qlist', 'namelist'];
+module.exports = { ConfigCategories, mappingConfig, productProperties, requiredProductProperties };
diff --git a/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json
new file mode 100644
index 00000000000..e04765faede
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json
@@ -0,0 +1,101 @@
+[
+ {
+ "sourceKeys": "properties.orderId",
+ "required": true,
+ "destKey": "ord"
+ },
+ {
+ "sourceKeys": ["properties.tr", "properties.ranSiteID"],
+ "required": true,
+ "destKey": "tr"
+ },
+ {
+ "sourceKeys": ["properties.land", "properties.landTime"],
+ "required": true,
+ "destKey": "land"
+ },
+ {
+ "sourceKeys": ["properties.date", "properties.orderCompletedTime"],
+ "destKey": "date"
+ },
+ {
+ "sourceKeys": ["properties.altord", "properties.alterOrderId"],
+ "destKey": "altord"
+ },
+ {
+ "sourceKeys": "properties.currency",
+ "destKey": "cur"
+ },
+ {
+ "sourceKeys": "properties.creditCardType",
+ "destKey": "cc"
+ },
+ {
+ "sourceKeys": "properties.commReason",
+ "destKey": "commreason"
+ },
+ {
+ "sourceKeys": "properties.isComm",
+ "destKey": "iscomm"
+ },
+ {
+ "sourceKeys": "properties.consumed",
+ "destKey": "consumed"
+ },
+ {
+ "sourceKeys": "properties.coupon",
+ "destKey": "coupon"
+ },
+ {
+ "sourceKeys": ["properties.custId", "properties.customerId", "properties.userId"],
+ "destKey": "custid"
+ },
+ {
+ "sourceKeys": ["properties.custScore", "properties.customerScore"],
+ "destKey": "custscore"
+ },
+ {
+ "sourceKeys": ["properties.custStatus", "properties.customerStatus"],
+ "destKey": "custstatus"
+ },
+ {
+ "sourceKeys": ["properties.dId", "properties.advertisingId"],
+ "destKey": "did"
+ },
+ {
+ "sourceKeys": ["properties.disamt", "properties.discountAmout"],
+ "destKey": "disamt"
+ },
+ {
+ "sourceKeys": ["properties.ordStatus", "properties.orderStatus"],
+ "destKey": "ordstatus"
+ },
+ {
+ "sourceKeys": "properties.segment",
+ "destKey": "segment"
+ },
+ {
+ "sourceKeys": "properties.shipcountry",
+ "destKey": "shipcountry"
+ },
+ {
+ "sourceKeys": "properties.shipped",
+ "destKey": "shipped"
+ },
+ {
+ "sourceKeys": ["properties.sitename", "properties.url", "context.page.url"],
+ "destKey": "sitename"
+ },
+ {
+ "sourceKeys": "properties.storeId",
+ "destKey": "storeid"
+ },
+ {
+ "sourceKeys": ["properties.storecat", "properties.storeCategory"],
+ "destKey": "storecat"
+ },
+ {
+ "sourceKeys": "properties.currency",
+ "destKey": "cur"
+ }
+]
diff --git a/src/cdk/v2/destinations/rakuten/procWorkflow.yaml b/src/cdk/v2/destinations/rakuten/procWorkflow.yaml
new file mode 100644
index 00000000000..9ee9b5c03a1
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/procWorkflow.yaml
@@ -0,0 +1,39 @@
+bindings:
+ - name: EventType
+ path: ../../../../constants
+ - path: ../../bindings/jsontemplate
+ - name: defaultRequestConfig
+ path: ../../../../v0/util
+ - name: removeUndefinedAndNullValues
+ path: ../../../../v0/util
+ - path: ./utils
+
+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])}}, "message type " + messageType + " is not supported");
+ $.assertConfig(.destination.Config.mid, "Merchant ID is not present. Aborting");
+ - name: prepareTrackPayload
+ condition: $.outputs.messageType === {{$.EventType.TRACK}}
+ template: |
+ const properties = $.constructProperties(.message);
+ const lineItems = $.constructLineItems(.message.properties)
+ $.context.payload = {...properties,...lineItems,xml:1, mid:.destination.Config.mid}
+ $.context.payload = $.removeUndefinedAndNullValues($.context.payload);
+
+ - name: buildResponse
+ template: |
+ const response = $.defaultRequestConfig();
+ response.params = $.context.payload;
+ response.method = "GET";
+ response.endpoint = "https://track.linksynergy.com/ep";
+ response.headers = {
+ "accept": "application/json",
+ "content-type": "application/json"
+ };
+ response
diff --git a/src/cdk/v2/destinations/rakuten/utils.js b/src/cdk/v2/destinations/rakuten/utils.js
new file mode 100644
index 00000000000..fe37455a572
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/utils.js
@@ -0,0 +1,70 @@
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+const { isDefinedAndNotNull } = require('rudder-transformer-cdk/build/utils');
+const {
+ mappingConfig,
+ ConfigCategories,
+ productProperties,
+ requiredProductProperties,
+} = require('./config');
+const { constructPayload } = require('../../../../v0/util');
+
+/**
+ * This fucntion constructs payloads based upon mappingConfig for Track call type
+ * @param {*} message
+ * @returns
+ */
+const constructProperties = (message) => {
+ const payload = constructPayload(message, mappingConfig[ConfigCategories.TRACK.name]);
+ return payload;
+};
+
+/**
+ * This fucntion build the item level list
+ * @param {*} properties
+ * @returns
+ */
+const constructLineItems = (properties) => {
+ // Validate the existence and non-emptiness of the 'products' array in 'properties'
+ if (!Array.isArray(properties?.products) || properties.products.length === 0) {
+ throw new InstrumentationError('Either properties.product is not an array or is empty');
+ }
+
+ const { products } = properties;
+ const productList = {};
+
+ // Iterate over product properties to construct the payload
+ Object.keys(productProperties).forEach((property) => {
+ const propertyKey = productProperties[property];
+
+ // Extract values for the current property from the 'products' array
+ const values = products.map((product) =>
+ isDefinedAndNotNull(product?.[propertyKey]) ? product[propertyKey] : '',
+ );
+
+ // Validate if a required property is missing
+ if (requiredProductProperties.includes(property) && values.includes('')) {
+ throw new InstrumentationError(`${propertyKey} is a required field. Aborting`);
+ }
+
+ // Include property in the payload if values are non-empty
+ if (values.some((element) => element !== '')) {
+ productList[property] = values.join('|');
+ }
+ });
+
+ // Map 'amountList' by evaluating 'amount' or deriving it from 'price' and 'quantity'
+ const amountList = products.map((product) => {
+ if (!product?.amount && !product?.price) {
+ throw new InstrumentationError('Either amount or price is required for every product');
+ }
+
+ if (product.price) {
+ return product.quantity * product.price * 100;
+ }
+ return product.amount * 100;
+ });
+ productList.amtlist = amountList.join('|');
+ return productList;
+};
+
+module.exports = { constructProperties, constructLineItems };
diff --git a/src/cdk/v2/destinations/rakuten/utils.test.js b/src/cdk/v2/destinations/rakuten/utils.test.js
new file mode 100644
index 00000000000..9cc7f5fd4cc
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/utils.test.js
@@ -0,0 +1,117 @@
+const { constructLineItems } = require('./utils');
+describe('constructLineItems', () => {
+ it('should return a non-empty object when given a valid properties object with at least one product', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ sku: 'sku_1',
+ price: 10,
+ quantity: 2,
+ amount: 20,
+ },
+ ],
+ };
+ const result = constructLineItems(properties);
+ const expectedObj = {
+ namelist: 'Product 1',
+ skulist: 'sku_1',
+ qlist: '2',
+ amtlist: '2000',
+ };
+ expect(result).toEqual(expectedObj);
+ });
+
+ it('should include all mapped properties in the returned object when present in at least one product', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ category: 'Category 1',
+ sku: 'sku_1',
+ brand: 'Brand 1',
+ price: 10,
+ quantity: 2,
+ amount: 20,
+ },
+ ],
+ };
+
+ const result = constructLineItems(properties);
+
+ const expectedObj = {
+ namelist: 'Product 1',
+ catlist: 'Category 1',
+ skulist: 'sku_1',
+ brandlist: 'Brand 1',
+ qlist: '2',
+ amtlist: '2000',
+ };
+ expect(result).toEqual(expectedObj);
+ });
+
+ it('should include amtlist property in the returned object with calculated values', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ sku: 'sku_1',
+ price: 10,
+ quantity: 2,
+ },
+ {
+ name: 'Product 2',
+ sku: 'sku_2',
+ price: 5,
+ quantity: 3,
+ },
+ ],
+ };
+
+ const result = constructLineItems(properties);
+
+ expect(result).toHaveProperty('amtlist');
+ expect(result.amtlist).toBe('2000|1500');
+ });
+
+ it('should throw an InstrumentationError when properties object is missing or has an empty products array', () => {
+ const properties = {};
+
+ expect(() => constructLineItems(properties)).toThrow(
+ 'Either properties.product is not an array or is empty',
+ );
+
+ properties.products = [];
+
+ expect(() => constructLineItems(properties)).toThrow(
+ 'Either properties.product is not an array or is empty',
+ );
+ });
+ it('should throw an InstrumentationError when a product is missing quantity property', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ sku: 'sku_1',
+ amount: '1234',
+ },
+ ],
+ };
+ expect(() => constructLineItems(properties)).toThrow('quantity is a required field. Aborting');
+ });
+ it('should throw an InstrumentationError when a product is missing both amount and price properties', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ sku: 'sku_1',
+ quantity: 2,
+ },
+ ],
+ };
+
+ expect(() => constructLineItems(properties)).toThrow(
+ 'Either amount or price is required for every product',
+ );
+ });
+});
diff --git a/src/v0/destinations/rakuten/networkHandler.js b/src/v0/destinations/rakuten/networkHandler.js
new file mode 100644
index 00000000000..1b16bd55389
--- /dev/null
+++ b/src/v0/destinations/rakuten/networkHandler.js
@@ -0,0 +1,103 @@
+const { NetworkError } = require('@rudderstack/integrations-lib');
+const { httpSend } = require('../../../adapters/network');
+const {
+ processAxiosResponse,
+ getDynamicErrorType,
+} = require('../../../adapters/utils/networkUtils');
+const { TAG_NAMES } = require('../../util/tags');
+const { HTTP_STATUS_CODES } = require('../../util/constant');
+
+const DESTINATION = 'RAKUTEN';
+const prepareProxyRequest = (request) => request;
+const proxyRequest = async (request, destType) => {
+ const { endpoint, data, method, params, headers } = prepareProxyRequest(request);
+ const requestOptions = {
+ url: endpoint,
+ data,
+ params,
+ headers,
+ method,
+ };
+ const response = await httpSend(requestOptions, { feature: 'proxy', destType });
+ return response;
+};
+const extractContent = (xmlPayload, tagName) => {
+ const pattern = new RegExp(`<${tagName}>(.*?)${tagName}>`);
+ const match = xmlPayload.match(pattern);
+ return match ? match[1] : null;
+};
+
+const responseHandler = (destinationResponse) => {
+ const msg = `[${DESTINATION} Response Handler] - Request Processed Successfully`;
+ const { response, status } = destinationResponse;
+ if (status === 400) {
+ throw new NetworkError(
+ `Request failed with status: ${status} due to invalid Marketing Id`,
+ 400,
+ {
+ [TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ destinationResponse,
+ );
+ }
+ // Extract errors, good and bad between different tags
+ const badRecords = extractContent(response, 'bad');
+ const errors = extractContent(response, 'error');
+
+ // For access denied for a mid rakuten sends status code 200 with response as Access denied
+ if (errors) {
+ throw new NetworkError(
+ `Request failed with status: ${status} due to ${errors}. Can you try to enable pixel tracking for this mid.`,
+ 400,
+ {
+ // status would be 200 but since no error type for this status code hence it will take it as aborted
+ [TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ destinationResponse,
+ );
+ }
+ if (parseInt(badRecords, 10)) {
+ throw new NetworkError(
+ `Request failed with status: ${status} with number of bad records ${badRecords}`,
+ 400,
+ {
+ // status would be 200 but since no error type for this status code hence it will take it as aborted
+ [TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ destinationResponse,
+ );
+ }
+ /* just puttting it here for 429 and 500's we dont have documentation for these two
+ neither we have any sample response but just in case if we recoeve non 2xx status
+ */
+ if (status !== 200) {
+ throw new NetworkError(
+ `Request failed with status: ${status}`,
+ status,
+ {
+ [TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ destinationResponse,
+ );
+ }
+ // if no error or bad record is found and status is 200 the request is successfull
+ return {
+ status: HTTP_STATUS_CODES.OK,
+ message: msg,
+ destinationResponse,
+ };
+};
+// eslint-disable-next-line @typescript-eslint/naming-convention
+class networkHandler {
+ constructor() {
+ this.responseHandler = responseHandler;
+ this.proxy = proxyRequest;
+ this.prepareProxy = prepareProxyRequest;
+ this.processAxiosResponse = processAxiosResponse;
+ }
+}
+
+module.exports = {
+ networkHandler,
+ responseHandler
+};
diff --git a/src/v0/destinations/rakuten/networkHandler.test.js b/src/v0/destinations/rakuten/networkHandler.test.js
new file mode 100644
index 00000000000..70461c86c17
--- /dev/null
+++ b/src/v0/destinations/rakuten/networkHandler.test.js
@@ -0,0 +1,64 @@
+const { responseHandler } = require('./networkHandler');
+// Generated by CodiumAI
+
+describe('responseHandler', () => {
+ it('should return a success message with status code 200 when the request is successful and no bad records or errors are found', () => {
+ const destinationResponse = {
+ response: '',
+ status: 200,
+ };
+
+ const result = responseHandler(destinationResponse);
+
+ expect(result.status).toBe(200);
+ expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully');
+ expect(result.destinationResponse).toEqual(destinationResponse);
+ });
+
+ it('should throw a NetworkError with status code 400 and error message when the response status is 400 due to invalid Marketing Id', () => {
+ const destinationResponse = {
+ response: 'Invalid marketing id',
+ status: 400,
+ };
+ expect(() => {
+ responseHandler(destinationResponse);
+ }).toThrow('Request failed with status: 400 due to invalid Marketing Id');
+ });
+
+ it('should throw a NetworkError with status code 400 and error message when the response contains errors', () => {
+ const destinationResponse = {
+ response: 'Access denied',
+ status: 200,
+ };
+ expect(() => {
+ responseHandler(destinationResponse);
+ }).toThrow(
+ 'Request failed with status: 200 due to Access denied. Can you try to enable pixel tracking for this mid.',
+ );
+ });
+
+ it('should return a success message with status code 200 when the response status is 200 and no bad records or errors are found', () => {
+ const destinationResponse = {
+ response: '',
+ status: 200,
+ };
+
+ const result = responseHandler(destinationResponse);
+
+ expect(result.status).toBe(200);
+ expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully');
+ expect(result.destinationResponse).toEqual(destinationResponse);
+ });
+
+ it('should throw a NetworkError with status code 400 and error message when the response status is 200 and the response contains only bad records', () => {
+ const destinationResponse = {
+ response: '1',
+ status: 200,
+ };
+
+ expect(() => {
+ responseHandler(destinationResponse);
+ }).toThrow('Request failed with status: 200 with number of bad records 1');
+
+ });
+});
diff --git a/test/integrations/destinations/rakuten/dataDelivery/data.ts b/test/integrations/destinations/rakuten/dataDelivery/data.ts
new file mode 100644
index 00000000000..2d2b00a5e4e
--- /dev/null
+++ b/test/integrations/destinations/rakuten/dataDelivery/data.ts
@@ -0,0 +1,203 @@
+import { endpoint, commonOutputHeaders } from '../processor/commonConfig';
+const commonParams = {
+ xml: 1,
+ amtlist: '12500|12500',
+ qlist: '|5',
+ ord: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ land: '20240129_1200',
+};
+export const data = [
+ {
+ name: 'rakuten',
+ description: 'Test 0: Failure response from rakuten for invalid mid',
+ feature: 'dataDelivery',
+ module: 'destination',
+ scenario: 'Framework',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ method: 'GET',
+ endpoint,
+ headers: commonOutputHeaders,
+ params: {
+ mid: 'invalid_mid',
+ ...commonParams,
+ },
+ userId: '',
+ },
+ },
+ },
+ output: {
+ response: {
+ status: 400,
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'configuration',
+ destType: 'RAKUTEN',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'dummyDestId',
+ workspaceId: 'dummyWorkspaceId',
+ },
+ destinationResponse: {
+ response:
+ '
HTTP Status 400 – Bad RequestHTTP Status 400 – Bad Request
',
+ status: 400,
+ rudderJobMetadata: [
+ {
+ jobId: 2,
+ attemptNum: 0,
+ userId: '',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestId',
+ workspaceId: 'dummyWorkspaceId',
+ },
+ ],
+ },
+ authErrorCategory: '',
+ message: 'Request failed with status: 400 due to invalid Marketing Id',
+ },
+ },
+ },
+ {
+ name: 'rakuten',
+ description: 'Test 1: Failure response from rakuten for access denied for rakuten mid',
+ feature: 'dataDelivery',
+ scenario: 'Framework',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ method: 'GET',
+ endpoint,
+ headers: commonOutputHeaders,
+ params: {
+ mid: 'access_denied_for_mid',
+ ...commonParams,
+ },
+ userId: '',
+ },
+ },
+ },
+ output: {
+ response: {
+ status: 400,
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'configuration',
+ destType: 'RAKUTEN',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'dummyDestId',
+ workspaceId: 'dummyWorkspaceId',
+ },
+ destinationResponse: {
+ response: 'Access denied',
+ status: 200,
+ rudderJobMetadata: [
+ {
+ jobId: 2,
+ attemptNum: 0,
+ userId: '',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestId',
+ workspaceId: 'dummyWorkspaceId',
+ },
+ ],
+ },
+ authErrorCategory: '',
+ message:
+ 'Request failed with status: 200 due to Access denied. Can you try to enable pixel tracking for this mid.',
+ },
+ },
+ },
+ {
+ name: 'rakuten',
+ description: 'Test 2: Failure response from rakuten for bad records>0',
+ feature: 'dataDelivery',
+ scenario: 'Framework',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ method: 'GET',
+ endpoint,
+ headers: commonOutputHeaders,
+ params: {
+ mid: 'valid_mid_with_bad_records',
+ ...commonParams,
+ },
+ userId: '',
+ },
+ },
+ },
+ output: {
+ response: {
+ status: 400,
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'RAKUTEN',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'dummyDestId',
+ workspaceId: 'dummyWorkspaceId',
+ },
+ destinationResponse: {
+ response:
+ '14340739143103',
+ status: 200,
+ rudderJobMetadata: [
+ {
+ jobId: 2,
+ attemptNum: 0,
+ userId: '',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestId',
+ workspaceId: 'dummyWorkspaceId',
+ },
+ ],
+ },
+ authErrorCategory: '',
+ message: 'Request failed with status: 200 with number of bad records 3',
+ },
+ },
+ },
+ {
+ name: 'rakuten',
+ description: 'Test 3: Success response from rakuten with good records > 0',
+ feature: 'dataDelivery',
+ scenario: 'Framework',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ method: 'GET',
+ endpoint,
+ headers: commonOutputHeaders,
+ params: {
+ mid: 'valid_mid_with_good_records',
+ ...commonParams,
+ },
+ userId: '',
+ },
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ destinationResponse:
+ 'uniqueId30',
+ message: '[RAKUTEN Response Handler] - Request Processed Successfully',
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/rakuten/network.ts b/test/integrations/destinations/rakuten/network.ts
new file mode 100644
index 00000000000..9633ee54a1a
--- /dev/null
+++ b/test/integrations/destinations/rakuten/network.ts
@@ -0,0 +1,98 @@
+export const networkCallsData = [
+ {
+ description: 'When mid is invalid',
+ httpReq: {
+ url: 'https://track.linksynergy.com/ep',
+ params: {
+ mid: 'invalid_mid',
+ xml: 1,
+ amtlist: '12500|12500',
+ qlist: '|5',
+ ord: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ land: '20240129_1200',
+ },
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ method: 'GET',
+ },
+ httpRes: {
+ status: 400,
+ data: 'HTTP Status 400 – Bad RequestHTTP Status 400 – Bad Request
',
+ },
+ },
+ {
+ description: 'When mid is valid but there is no access',
+ httpReq: {
+ url: 'https://track.linksynergy.com/ep',
+ params: {
+ mid: 'access_denied_for_mid',
+ xml: 1,
+ amtlist: '12500|12500',
+ qlist: '|5',
+ ord: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ land: '20240129_1200',
+ },
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ method: 'GET',
+ },
+ httpRes: {
+ status: 200,
+ data: 'Access denied',
+ },
+ },
+ {
+ description: 'When record along with mid is valid',
+ httpReq: {
+ url: 'https://track.linksynergy.com/ep',
+ params: {
+ mid: 'valid_mid_with_good_records',
+ xml: 1,
+ amtlist: '12500|12500',
+ qlist: '|5',
+ ord: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ land: '20240129_1200',
+ },
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ method: 'GET',
+ },
+ httpRes: {
+ status: 200,
+ data: 'uniqueId30',
+ },
+ },
+ {
+ description: 'When records are invalid and mid is valid',
+ httpReq: {
+ url: 'https://track.linksynergy.com/ep',
+ params: {
+ mid: 'valid_mid_with_bad_records',
+ xml: 1,
+ amtlist: '12500|12500',
+ qlist: '|5',
+ ord: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ land: '20240129_1200',
+ },
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ method: 'GET',
+ },
+ httpRes: {
+ status: 200,
+ data: 'uniqueId03',
+ },
+ },
+];
diff --git a/test/integrations/destinations/rakuten/processor/commonConfig.ts b/test/integrations/destinations/rakuten/processor/commonConfig.ts
new file mode 100644
index 00000000000..e7e2af7fbd1
--- /dev/null
+++ b/test/integrations/destinations/rakuten/processor/commonConfig.ts
@@ -0,0 +1,65 @@
+export const destination = {
+ ID: 'random_id',
+ Name: 'rakuten',
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ mid: 'dummyMarketingId',
+ },
+};
+export const endpoint = 'https://track.linksynergy.com/ep';
+export const commonOutputHeaders = {
+ accept: 'application/json',
+ 'content-type': 'application/json',
+};
+export const singleProductWithAllProperties = {
+ sku: 'ABC123',
+ amount: 20,
+ quantity: 5,
+ name: 'SampleProduct',
+ brand: 'SampleBrand',
+ coupon: 'SALE20',
+ categoryId: '12345',
+ category: 'Electronics',
+ discountAmount: 10.5,
+ discountType: 'Percentage',
+ isClearance: 'Y',
+ isMarketPlace: 'N',
+ isSale: 'Y',
+ itmStatus: 'In Stock',
+ margin: 0.15,
+ markdown: 5.0,
+ shipId: 'SHIP123',
+ shipBy: 'Express',
+ taxExempt: 'N',
+ sequence: '123',
+ isComm: 'Y',
+};
+export const commonProperties = {
+ orderId: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ landTime: '20240129_1200',
+ date: '20240129_1300',
+ altord: 'SampleAlternateOrderId',
+ currency: 'INR',
+ creditCardType: 'Visa',
+ commReason: 'SampleCommReason',
+ isComm: 'Y',
+ consumed: '20240129_1400',
+ coupon: 'SampleCoupon',
+ custId: 'SampleCustomerId',
+ custScore: 'A',
+ custStatus: 'New',
+ dId: 'SampleDeviceId',
+ disamt: '50.00',
+ ordStatus: 'Pending',
+ segment: 'SampleSegment',
+ shipcountry: 'USA',
+ shipped: '20240129_1500',
+ sitename: 'SampleSiteName',
+ storeId: '12345',
+ storecat: 'Electronics',
+};
diff --git a/test/integrations/destinations/rakuten/processor/data.ts b/test/integrations/destinations/rakuten/processor/data.ts
new file mode 100644
index 00000000000..bdce4e850ec
--- /dev/null
+++ b/test/integrations/destinations/rakuten/processor/data.ts
@@ -0,0 +1,3 @@
+import { transformationFailures } from './transformationFailure';
+import { trackSuccess } from './track';
+export const data = [...trackSuccess, ...transformationFailures];
diff --git a/test/integrations/destinations/rakuten/processor/track.ts b/test/integrations/destinations/rakuten/processor/track.ts
new file mode 100644
index 00000000000..78a76e42632
--- /dev/null
+++ b/test/integrations/destinations/rakuten/processor/track.ts
@@ -0,0 +1,448 @@
+import {
+ destination,
+ commonOutputHeaders,
+ commonProperties,
+ endpoint,
+ singleProductWithAllProperties,
+} from './commonConfig';
+import { transformResultBuilder } from '../../../testUtils';
+export const trackSuccess = [
+ {
+ id: 'rakuten-test-track-success-1',
+ name: 'rakuten',
+ description:
+ 'Track call with properties.products with all properties in payload and one product containing all product properties and other containg some',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should contain only properties and product payload and status code should be 200',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ message: {
+ type: 'track',
+ event: 'product purchased',
+ sentAt: '2021-01-25T16:12:02.048Z',
+ userId: 'sajal12',
+ channel: 'mobile',
+ rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c',
+ messageId: '1611588776408-ee5a3212-fbf9-4cbb-bbad-3ed0f7c6a2ce',
+ properties: {
+ ...commonProperties,
+ products: [
+ { ...singleProductWithAllProperties },
+ {
+ sku: 'custom sku 1',
+ quantity: 5,
+ amount: 25,
+ name: 'name_1',
+ },
+ {
+ sku: 'custom sku 2',
+ name: 'SampleProduct',
+ quantity: 1,
+ amount: 30,
+ coupon: 'SALE50',
+ },
+ ],
+ },
+ anonymousId: '9c6bd77ea9da3e68',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ },
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ output: transformResultBuilder({
+ method: 'GET',
+ endpoint,
+ headers: commonOutputHeaders,
+ params: {
+ mid: 'dummyMarketingId',
+ xml: 1,
+ amtlist: '2000|2500|3000',
+ brandlist: 'SampleBrand||',
+ catidlist: '12345||',
+ catlist: 'Electronics||',
+ couponlist: 'SALE20||SALE50',
+ disamtlist: '10.5||',
+ distypelist: 'Percentage||',
+ ismarketplacelist: 'N||',
+ sequencelist: '123||',
+ shipbylist: 'Express||',
+ shipidlist: 'SHIP123||',
+ qlist: '5|5|1',
+ marginlist: '0.15||',
+ markdownlist: '5||',
+ taxexemptlist: 'N||',
+ namelist: 'SampleProduct|name_1|SampleProduct',
+ skulist: 'ABC123|custom sku 1|custom sku 2',
+ issalelist: 'Y||',
+ itmstatuslist: 'In Stock||',
+ isclearancelist: 'Y||',
+ ord: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ land: '20240129_1200',
+ date: '20240129_1300',
+ altord: 'SampleAlternateOrderId',
+ cur: 'INR',
+ cc: 'Visa',
+ commreason: 'SampleCommReason',
+ iscomm: 'Y',
+ consumed: '20240129_1400',
+ coupon: 'SampleCoupon',
+ custid: 'SampleCustomerId',
+ custscore: 'A',
+ custstatus: 'New',
+ did: 'SampleDeviceId',
+ disamt: '50.00',
+ ordstatus: 'Pending',
+ segment: 'SampleSegment',
+ shipcountry: 'USA',
+ shipped: '20240129_1500',
+ sitename: 'SampleSiteName',
+ storeid: '12345',
+ storecat: 'Electronics',
+ },
+ userId: '',
+ }),
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'rakuten-test-track-success-2',
+ name: 'rakuten',
+ description:
+ 'Track call with properties.products and no event in payload and products containing amount,price and quantity',
+ scenario: 'Business+Framework',
+ successCriteria:
+ 'Response should contain only properties and product payload and amount to be calculated from price*quantity where amount is not present and quantity taken as 1 by default and status code should be 200',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ message: {
+ type: 'track',
+ sentAt: '2021-01-25T16:12:02.048Z',
+ userId: 'sajal12',
+ channel: 'mobile',
+ rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c',
+ messageId: '1611588776408-ee5a3212-fbf9-4cbb-bbad-3ed0f7c6a2ce',
+ properties: {
+ ...commonProperties,
+ products: [
+ {
+ sku: 'custom sku 0',
+ amount: '125',
+ quantity: 1,
+ name: 'name_1',
+ },
+ {
+ sku: 'custom sku 1',
+ quantity: 5,
+ price: 25,
+ name: 'name_2',
+ },
+ {
+ sku: 'custom sku 2',
+ name: 'SampleProduct',
+ price: 30,
+ quantity: 1,
+ coupon: 'SALE50',
+ },
+ ],
+ },
+ anonymousId: '9c6bd77ea9da3e68',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ },
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ output: transformResultBuilder({
+ method: 'GET',
+ endpoint,
+ headers: commonOutputHeaders,
+ params: {
+ mid: 'dummyMarketingId',
+ xml: 1,
+ amtlist: '12500|12500|3000',
+ couponlist: '||SALE50',
+ namelist: 'name_1|name_2|SampleProduct',
+ skulist: 'custom sku 0|custom sku 1|custom sku 2',
+ qlist: '1|5|1',
+ ord: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ land: '20240129_1200',
+ date: '20240129_1300',
+ altord: 'SampleAlternateOrderId',
+ cur: 'INR',
+ cc: 'Visa',
+ commreason: 'SampleCommReason',
+ iscomm: 'Y',
+ consumed: '20240129_1400',
+ coupon: 'SampleCoupon',
+ custid: 'SampleCustomerId',
+ custscore: 'A',
+ custstatus: 'New',
+ did: 'SampleDeviceId',
+ disamt: '50.00',
+ ordstatus: 'Pending',
+ segment: 'SampleSegment',
+ shipcountry: 'USA',
+ shipped: '20240129_1500',
+ sitename: 'SampleSiteName',
+ storeid: '12345',
+ storecat: 'Electronics',
+ },
+ userId: '',
+ }),
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'rakuten-test-track-success-3',
+ name: 'rakuten',
+ description:
+ 'Track call for products return or cancelled products containing amount,price and quantity where price is negative',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should contain only properties and product payload and amount to be calculated from price*quantity where amount is negative and status code should be 200',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ message: {
+ type: 'track',
+ sentAt: '2021-01-25T16:12:02.048Z',
+ userId: 'sajal12',
+ channel: 'mobile',
+ rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c',
+ messageId: '1611588776408-ee5a3212-fbf9-4cbb-bbad-3ed0f7c6a2ce',
+ properties: {
+ ...commonProperties,
+ products: [
+ {
+ sku: 'custom sku 0',
+ quantity: 1,
+ amount: '-125',
+ name: 'name_1',
+ },
+ {
+ sku: 'custom sku 1',
+ quantity: 5,
+ price: -25,
+ name: 'name_2',
+ },
+ ],
+ },
+ anonymousId: '9c6bd77ea9da3e68',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ },
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ output: transformResultBuilder({
+ method: 'GET',
+ endpoint,
+ headers: commonOutputHeaders,
+ params: {
+ mid: 'dummyMarketingId',
+ xml: 1,
+ amtlist: '-12500|-12500',
+ skulist: 'custom sku 0|custom sku 1',
+ qlist: '1|5',
+ ord: 'SampleOrderId',
+ namelist: 'name_1|name_2',
+ tr: 'SampleRanSiteID',
+ land: '20240129_1200',
+ date: '20240129_1300',
+ altord: 'SampleAlternateOrderId',
+ cur: 'INR',
+ cc: 'Visa',
+ commreason: 'SampleCommReason',
+ iscomm: 'Y',
+ consumed: '20240129_1400',
+ coupon: 'SampleCoupon',
+ custid: 'SampleCustomerId',
+ custscore: 'A',
+ custstatus: 'New',
+ did: 'SampleDeviceId',
+ disamt: '50.00',
+ ordstatus: 'Pending',
+ segment: 'SampleSegment',
+ shipcountry: 'USA',
+ shipped: '20240129_1500',
+ sitename: 'SampleSiteName',
+ storeid: '12345',
+ storecat: 'Electronics',
+ },
+ userId: '',
+ }),
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'rakuten-test-track-success-4',
+ name: 'rakuten',
+ description: 'Track call for Discount event ',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should have last item of skulist as "Discount", qlist as 0 and amtlist as negative and status code should be 200',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ message: {
+ type: 'track',
+ sentAt: '2021-01-25T16:12:02.048Z',
+ userId: 'sajal12',
+ channel: 'mobile',
+ rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c',
+ messageId: '1611588776408-ee5a3212-fbf9-4cbb-bbad-3ed0f7c6a2ce',
+ properties: {
+ orderId: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ landTime: '20240129_1200',
+ products: [
+ {
+ sku: 'custom sku 0',
+ quantity: 5,
+ amount: '125',
+ name: 'name_1',
+ },
+ {
+ sku: 'custom sku 1',
+ quantity: 5,
+ price: 25,
+ name: 'name_2',
+ },
+ {
+ sku: 'Discount',
+ quantity: 0,
+ amount: -500,
+ name: 'Discount',
+ },
+ ],
+ },
+ anonymousId: '9c6bd77ea9da3e68',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ },
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ output: transformResultBuilder({
+ method: 'GET',
+ endpoint,
+ headers: commonOutputHeaders,
+ params: {
+ mid: 'dummyMarketingId',
+ xml: 1,
+ namelist: 'name_1|name_2|Discount',
+ amtlist: '12500|12500|-50000',
+ skulist: 'custom sku 0|custom sku 1|Discount',
+ qlist: '5|5|0',
+ ord: 'SampleOrderId',
+ tr: 'SampleRanSiteID',
+ land: '20240129_1200',
+ },
+ userId: '',
+ }),
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/rakuten/processor/transformationFailure.ts b/test/integrations/destinations/rakuten/processor/transformationFailure.ts
new file mode 100644
index 00000000000..906ddafd6ac
--- /dev/null
+++ b/test/integrations/destinations/rakuten/processor/transformationFailure.ts
@@ -0,0 +1,335 @@
+import { destination } from './commonConfig';
+
+export const transformationFailures = [
+ {
+ id: 'rakuten-test-2',
+ name: 'rakuten',
+ description: 'Required field orderId not present',
+ scenario: 'Framework',
+ successCriteria: 'Transformationn Error for orderId not present',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ message: {
+ type: 'track',
+ event: 'product purchased',
+ sentAt: '2021-01-25T16:12:02.048Z',
+ userId: 'sajal12',
+ channel: 'mobile',
+ rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c',
+ messageId: '1611588776408-ee5a3212-fbf9-4cbb-bbad-3ed0f7c6a2ce',
+ properties: {
+ products: [{}],
+ },
+ anonymousId: '9c6bd77ea9da3e68',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ },
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'Missing required value from "properties.orderId": Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from "properties.orderId"',
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ statTags: {
+ destType: 'RAKUTEN',
+ destinationId: 'dummyDestId',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'rakuten-test-3',
+ name: 'rakuten',
+ description: 'No products available in products array to send',
+ scenario: 'Framework',
+ successCriteria: 'Transformationn Error for no products present to send',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ message: {
+ type: 'track',
+ event: 'product purchased',
+ sentAt: '2021-01-25T16:12:02.048Z',
+ userId: 'sajal12',
+ channel: 'mobile',
+ rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c',
+ messageId: '1611588776408-ee5a3212-fbf9-4cbb-bbad-3ed0f7c6a2ce',
+ properties: {
+ land: '20230406_2342',
+ tr: 'txnId',
+ orderId: 'ord 123',
+ products: [],
+ },
+ anonymousId: '9c6bd77ea9da3e68',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ },
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'Either properties.product is not an array or is empty: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Either properties.product is not an array or is empty',
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ statTags: {
+ destType: 'RAKUTEN',
+ destinationId: 'dummyDestId',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'rakuten-test-4',
+ name: 'rakuten',
+ description: 'Unsupported message type -> Identify',
+ scenario: 'Framework',
+ successCriteria: 'Transformationn Error for Unsupported message type',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ message: {
+ type: 'identify',
+ sentAt: '2021-01-25T16:12:02.048Z',
+ userId: 'sajal12',
+ channel: 'mobile',
+ rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c',
+ messageId: '1611588776408-ee5a3212-fbf9-4cbb-bbad-3ed0f7c6a2ce',
+ traits: {
+ orderId: 'ord 123',
+ products: [],
+ },
+ anonymousId: '9c6bd77ea9da3e68',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ },
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'message type identify is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type identify is not supported',
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ statTags: {
+ destType: 'RAKUTEN',
+ errorCategory: 'dataValidation',
+ destinationId: 'dummyDestId',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'rakuten-test-5',
+ name: 'rakuten',
+ description: 'No eligible property available for required field tr present',
+ scenario: 'Framework',
+ successCriteria: 'Transformationn Error for required field tr not present',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ message: {
+ type: 'track',
+ event: 'product purchased',
+ sentAt: '2021-01-25T16:12:02.048Z',
+ userId: 'sajal12',
+ channel: 'mobile',
+ rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c',
+ messageId: '1611588776408-ee5a3212-fbf9-4cbb-bbad-3ed0f7c6a2ce',
+ properties: {
+ orderId: 'ord 123',
+ products: [],
+ },
+ anonymousId: '9c6bd77ea9da3e68',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ },
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ 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"]',
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ statTags: {
+ destType: 'RAKUTEN',
+ destinationId: 'dummyDestId',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'rakuten-test-6',
+ name: 'rakuten',
+ description: 'No eligible property available for required field land present',
+ scenario: 'Framework',
+ successCriteria: 'Transformationn Error for required field land not present',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ message: {
+ type: 'track',
+ event: 'product purchased',
+ sentAt: '2021-01-25T16:12:02.048Z',
+ userId: 'sajal12',
+ channel: 'mobile',
+ rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c',
+ messageId: '1611588776408-ee5a3212-fbf9-4cbb-bbad-3ed0f7c6a2ce',
+ properties: {
+ tr: 'txnId',
+ orderId: 'ord 123',
+ products: [],
+ },
+ anonymousId: '9c6bd77ea9da3e68',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ },
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ 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"]',
+ metadata: {
+ destinationId: 'dummyDestId',
+ jobId: '1',
+ },
+ statTags: {
+ destType: 'RAKUTEN',
+ destinationId: 'dummyDestId',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/testUtils.ts b/test/integrations/testUtils.ts
index 09f3a82d40b..1eb1f692aa5 100644
--- a/test/integrations/testUtils.ts
+++ b/test/integrations/testUtils.ts
@@ -38,7 +38,7 @@ export const getAllTestMockDataFilePaths = (dirPath: string, destination: string
};
export const addMock = (mock: MockAdapter, axiosMock: MockHttpCallsData) => {
- const { url, method, data: reqData, ...opts } = axiosMock.httpReq;
+ const { url, method, data: reqData, params, ...opts } = axiosMock.httpReq;
const { data, headers, status } = axiosMock.httpRes;
const headersAsymMatch = {
@@ -49,8 +49,10 @@ 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,
+ // particularly for GET requests where it is typically unnecessary
// @ts-ignore
- mock.onGet(url, reqData, headersAsymMatch).reply(status, data, headers);
+ mock.onGet(url, { params }, headersAsymMatch).reply(status, data, headers);
break;
case 'delete':
// @ts-ignore