Skip to content

Commit 038048e

Browse files
[Amazon CAPI] - adding validation and new setting field (#3682)
* adding validation and new setting field * updating description * updating new field description
1 parent 5cea54d commit 038048e

5 files changed

Lines changed: 168 additions & 4 deletions

File tree

packages/destination-actions/src/destinations/amazon-conversions-api/__tests__/index.test.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Basic test to confirm destination structure
22
import Destination from '../index'
3-
import type { DestinationDefinition } from '@segment/actions-core'
3+
import type { DestinationDefinition, RequestClient } from '@segment/actions-core'
4+
import { Region } from '../types'
45

56
describe('Amazon Conversions API Destination', () => {
67
it('should be properly configured', () => {
@@ -113,4 +114,96 @@ describe('Amazon Conversions API Destination', () => {
113114
}
114115
})
115116
})
117+
118+
describe('testAuthentication', () => {
119+
const testAuth = Destination.authentication.testAuthentication!
120+
let mockRequest: jest.Mock
121+
122+
beforeEach(() => {
123+
mockRequest = jest.fn().mockResolvedValue({ status: 200 })
124+
})
125+
126+
it('should throw for non-numeric advertiserId', async () => {
127+
await expect(
128+
testAuth(mockRequest as unknown as RequestClient, {
129+
auth: { accessToken: 'valid-token', refreshToken: '' },
130+
settings: { region: Region.NA, advertiserId: 'abc123' }
131+
} as any)
132+
).rejects.toThrow('Advertising ID must be numeric')
133+
})
134+
135+
it('should throw for advertiserId not numeric', async () => {
136+
await expect(
137+
testAuth(mockRequest as unknown as RequestClient, {
138+
auth: { accessToken: 'valid-token', refreshToken: '' },
139+
settings: { region: Region.NA, advertiserId: '1234ac' }
140+
} as any)
141+
).rejects.toThrow('Advertising ID must be numeric')
142+
})
143+
144+
it('should throw for dataSetName that is too short (under 5 chars)', async () => {
145+
await expect(
146+
testAuth(mockRequest as unknown as RequestClient, {
147+
auth: { accessToken: 'valid-token', refreshToken: '' },
148+
settings: { region: Region.NA, advertiserId: '12345', dataSetName: 'abcd' }
149+
} as any)
150+
).rejects.toThrow('Dataset Name must start with a letter and can only contain letters, numbers, underscores, or hyphens.')
151+
})
152+
153+
it('should throw for dataSetName that starts with a digit', async () => {
154+
await expect(
155+
testAuth(mockRequest as unknown as RequestClient, {
156+
auth: { accessToken: 'valid-token', refreshToken: '' },
157+
settings: { region: Region.NA, advertiserId: '12345', dataSetName: '1invalid' }
158+
} as any)
159+
).rejects.toThrow('Dataset Name must start with a letter and can only contain letters, numbers, underscores, or hyphens.')
160+
})
161+
162+
it('should throw for dataSetName with invalid characters', async () => {
163+
await expect(
164+
testAuth(mockRequest as unknown as RequestClient, {
165+
auth: { accessToken: 'valid-token', refreshToken: '' },
166+
settings: { region: Region.NA, advertiserId: '12345', dataSetName: 'my dataset!' }
167+
} as any)
168+
).rejects.toThrow('Dataset Name must start with a letter and can only contain letters, numbers, underscores, or hyphens.')
169+
})
170+
171+
it('should pass with a valid dataSetName', async () => {
172+
await expect(
173+
testAuth(mockRequest as unknown as RequestClient, {
174+
auth: { accessToken: 'valid-token', refreshToken: '' },
175+
settings: { region: Region.NA, advertiserId: '12345', dataSetName: 'ValidDataset1' }
176+
} as any)
177+
).resolves.toBeDefined()
178+
expect(mockRequest).toHaveBeenCalled()
179+
})
180+
181+
it('should pass with a dataSetName using underscores and hyphens', async () => {
182+
await expect(
183+
testAuth(mockRequest as unknown as RequestClient, {
184+
auth: { accessToken: 'valid-token', refreshToken: '' },
185+
settings: { region: Region.NA, advertiserId: '12345', dataSetName: 'Default_Events-2' }
186+
} as any)
187+
).resolves.toBeDefined()
188+
})
189+
190+
it('should pass without dataSetName (field is optional)', async () => {
191+
await expect(
192+
testAuth(mockRequest as unknown as RequestClient, {
193+
auth: { accessToken: 'valid-token', refreshToken: '' },
194+
settings: { region: Region.NA, advertiserId: '12345' }
195+
} as any)
196+
).resolves.toBeDefined()
197+
expect(mockRequest).toHaveBeenCalled()
198+
})
199+
200+
it('should pass with numeric advertiserId', async () => {
201+
await expect(
202+
testAuth(mockRequest as unknown as RequestClient, {
203+
auth: { accessToken: 'valid-token', refreshToken: '' },
204+
settings: { region: Region.NA, advertiserId: '9876543210' }
205+
} as any)
206+
).resolves.toBeDefined()
207+
})
208+
})
116209
})

packages/destination-actions/src/destinations/amazon-conversions-api/generated-types.ts

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/destination-actions/src/destinations/amazon-conversions-api/index.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,32 @@ const destination: DestinationDefinition<Settings> = {
3030
},
3131
advertiserId: {
3232
label: 'Amazon Advertiser ID',
33-
description: 'Your Amazon Advertiser Account ID.',
33+
description: 'Your Amazon Advertiser Account ID. This must be a numeric value. Use Amazon DSP CFID and not Entity ID.',
3434
type: 'string',
3535
required: true
36+
},
37+
dataSetName: {
38+
label: 'Dataset Name',
39+
description: 'Amazon Ads organizes uploaded data into datasets, which are logical groupings used to separate and categorize events from your sources. The default dataset name (if not provided) is Default_Events. All events within a dataset will appear in Amazon Ads Data Manager under the name you provide here. New destination? We recommend providing a dataset name during initial setup. Existing destination? We strongly recommend reading the [FAQ](https://www.twilio.com/docs/segment/connections/destinations/catalog/actions-amazon-conversions-api#what-is-a-dataset-and-how-does-amazon-use-the-dataset-name) before updating your dataset name, as changes may impact your existing events.',
40+
type: 'string',
41+
required: false
3642
}
3743
},
3844
testAuthentication: async (request, { auth, settings }) => {
3945
if (!auth?.accessToken) {
4046
throw new InvalidAuthenticationError('Please authenticate via Oauth before enabling the destination.')
4147
}
4248

49+
const { dataSetName, advertiserId } = settings
50+
51+
if(dataSetName && !/^[A-Za-z][A-Za-z0-9_-]{4,99}$/.test(dataSetName ?? '')){
52+
throw new InvalidAuthenticationError('Dataset Name must start with a letter and can only contain letters, numbers, underscores, or hyphens. It must be between 5 and 100 characters long.')
53+
}
54+
55+
if(!/^\d+$/.test(advertiserId)) {
56+
throw new InvalidAuthenticationError('Advertising ID must be numeric')
57+
}
58+
4359
return await request<RefreshTokenResponse>(`${settings.region}/v2/profiles`, {
4460
method: 'GET',
4561
headers: {

packages/destination-actions/src/destinations/amazon-conversions-api/trackConversion/__tests__/utils.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,56 @@ describe('trackConversion utils', () => {
628628
expect(result.unitsSold).toBeUndefined()
629629
})
630630

631+
it('should include dataSetName in eventDescription when provided in settings', () => {
632+
const settingsWithDataSet = { ...settings, dataSetName: 'MyDataset1' }
633+
const payload: Payload = {
634+
name: 'test_event',
635+
eventType: ConversionTypeV2.PAGE_VIEW,
636+
eventActionSource: 'WEBSITE',
637+
countryCode: 'US',
638+
timestamp: '2023-01-01T12:00:00Z',
639+
matchKeys: { email: '[email protected]' },
640+
enable_batching: true
641+
}
642+
643+
const result = prepareEventData(payload, settingsWithDataSet)
644+
645+
expect(result.eventDescription.dataSetName).toBe('MyDataset1')
646+
})
647+
648+
it('should not include dataSetName in eventDescription when absent from settings', () => {
649+
const payload: Payload = {
650+
name: 'test_event',
651+
eventType: ConversionTypeV2.PAGE_VIEW,
652+
eventActionSource: 'WEBSITE',
653+
countryCode: 'US',
654+
timestamp: '2023-01-01T12:00:00Z',
655+
matchKeys: { email: '[email protected]' },
656+
enable_batching: true
657+
}
658+
659+
const result = prepareEventData(payload, settings)
660+
661+
expect(result.eventDescription.dataSetName).toBeUndefined()
662+
})
663+
664+
it('should not include dataSetName in eventDescription when settings.dataSetName is empty string', () => {
665+
const settingsWithEmptyDataSet = { ...settings, dataSetName: '' }
666+
const payload: Payload = {
667+
name: 'test_event',
668+
eventType: ConversionTypeV2.PAGE_VIEW,
669+
eventActionSource: 'WEBSITE',
670+
countryCode: 'US',
671+
timestamp: '2023-01-01T12:00:00Z',
672+
matchKeys: { email: '[email protected]' },
673+
enable_batching: true
674+
}
675+
676+
const result = prepareEventData(payload, settingsWithEmptyDataSet)
677+
678+
expect(result.eventDescription.dataSetName).toBeUndefined()
679+
})
680+
631681
it('should include optional fields when provided', () => {
632682
const payload: Payload = {
633683
name: 'purchase_event',

packages/destination-actions/src/destinations/amazon-conversions-api/trackConversion/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,8 @@ export function prepareEventData(payload: Payload, settings: Settings): EventDat
379379
name: payload.name,
380380
conversionType: payload.eventType as ConversionTypeV2,
381381
eventSource: payload.eventActionSource.toUpperCase(),
382-
eventIngestionMethod: 'SERVER_TO_SERVER'
382+
eventIngestionMethod: 'SERVER_TO_SERVER',
383+
...(settings.dataSetName ? { dataSetName: settings.dataSetName } : {})
383384
}
384385

385386
const eventData: EventData = {

0 commit comments

Comments
 (0)