diff --git a/src/v0/sources/adjust/transform.js b/src/v0/sources/adjust/transform.js index 9da90751b7..f68e87d476 100644 --- a/src/v0/sources/adjust/transform.js +++ b/src/v0/sources/adjust/transform.js @@ -7,6 +7,7 @@ const Message = require('../message'); const { CommonUtils } = require('../../../util/common'); const { excludedFieldList } = require('./config'); const { extractCustomFields, generateUUID } = require('../../util'); +const { convertToISODate } = require('./utils'); // ref : https://help.adjust.com/en/article/global-callbacks#general-recommended-placeholders // import mapping json using JSON.parse to preserve object key order @@ -43,11 +44,10 @@ const processEvent = (inputEvent) => { message.properties = { ...message.properties, ...customProperties }; if (formattedPayload.created_at) { - const ts = new Date(formattedPayload.created_at * 1000).toISOString(); + const ts = convertToISODate(formattedPayload.created_at); message.setProperty('originalTimestamp', ts); message.setProperty('timestamp', ts); } - // adjust does not has the concept of user but we need to set some random anonymousId in order to make the server accept the message message.anonymousId = generateUUID(); return message; diff --git a/src/v0/sources/adjust/utils.js b/src/v0/sources/adjust/utils.js new file mode 100644 index 0000000000..73ec696e34 --- /dev/null +++ b/src/v0/sources/adjust/utils.js @@ -0,0 +1,27 @@ +const { TransformationError } = require('@rudderstack/integrations-lib'); + +const convertToISODate = (rawTimestamp) => { + if (typeof rawTimestamp !== 'number' && typeof rawTimestamp !== 'string') { + throw new TransformationError( + `Invalid timestamp type: expected number or string, received ${typeof rawTimestamp}`, + ); + } + + const createdAt = Number(rawTimestamp); + + if (Number.isNaN(createdAt)) { + throw new TransformationError(`Failed to parse timestamp: "${rawTimestamp}"`); + } + + const date = new Date(createdAt * 1000); + + if (Number.isNaN(date.getTime())) { + throw new TransformationError(`Failed to create valid date for timestamp "${rawTimestamp}"`); + } + + return date.toISOString(); +}; + +module.exports = { + convertToISODate, +}; diff --git a/src/v0/sources/adjust/utils.test.js b/src/v0/sources/adjust/utils.test.js new file mode 100644 index 0000000000..f5a0caa832 --- /dev/null +++ b/src/v0/sources/adjust/utils.test.js @@ -0,0 +1,37 @@ +const { convertToISODate } = require('./utils'); +const { TransformationError } = require('@rudderstack/integrations-lib'); + +describe('convertToISODate', () => { + // Converts valid numeric timestamp to ISO date string + it('should return ISO date string when given a valid numeric timestamp', () => { + const timestamp = 1633072800; // Example timestamp for 2021-10-01T00:00:00.000Z + const result = convertToISODate(timestamp); + expect(result).toBe('2021-10-01T07:20:00.000Z'); + }); + + // Throws error for non-numeric string input + it('should throw TransformationError when given a non-numeric string', () => { + const invalidTimestamp = 'invalid'; + expect(() => convertToISODate(invalidTimestamp)).toThrow(TransformationError); + }); + + // Converts valid numeric string timestamp to ISO date string + it('should convert valid numeric string timestamp to ISO date string', () => { + const rawTimestamp = '1633072800'; // Corresponds to 2021-10-01T00:00:00.000Z + const result = convertToISODate(rawTimestamp); + expect(result).toBe('2021-10-01T07:20:00.000Z'); + }); + + // Throws error for non-number and non-string input + it('should throw error for non-number and non-string input', () => { + expect(() => convertToISODate({})).toThrow(TransformationError); + expect(() => convertToISODate([])).toThrow(TransformationError); + expect(() => convertToISODate(null)).toThrow(TransformationError); + expect(() => convertToISODate(undefined)).toThrow(TransformationError); + }); + + it('should throw error for timestamp that results in invalid date when multiplied', () => { + const hugeTimestamp = 999999999999999; // This will become invalid when multiplied by 1000 + expect(() => convertToISODate(hugeTimestamp)).toThrow(TransformationError); + }); +}); diff --git a/test/integrations/sources/adjust/data.ts b/test/integrations/sources/adjust/data.ts index e57feb45d4..107bb444c4 100644 --- a/test/integrations/sources/adjust/data.ts +++ b/test/integrations/sources/adjust/data.ts @@ -125,4 +125,57 @@ export const data = [ defaultMockFns(); }, }, + { + name: 'adjust', + description: 'Simple track call with wrong created at', + module: 'source', + version: 'v0', + skipGo: 'FIXME', + input: { + request: { + body: [ + { + id: 'adjust', + query_parameters: { + gps_adid: ['38400000-8cf0-11bd-b23e-10b96e40000d'], + adid: ['18546f6171f67e29d1cb983322ad1329'], + tracker_token: ['abc'], + custom: ['custom'], + tracker_name: ['dummy'], + created_at: ['test'], + event_name: ['Click'], + }, + updated_at: '2023-02-10T12:16:07.251Z', + created_at: 'test', + }, + ], + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, + pathSuffix: '', + }, + output: { + response: { + status: 200, + body: [ + { + error: 'Failed to parse timestamp: "test"', + statTags: { + destinationId: 'Non determinable', + errorCategory: 'transformation', + implementation: 'native', + module: 'source', + workspaceId: 'Non determinable', + }, + statusCode: 400, + }, + ], + }, + }, + mockFns: () => { + defaultMockFns(); + }, + }, ];