Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,60 @@ describe('LinkedinAudiences.updateAudience', () => {
})

describe('Error cases', () => {
it('should throw InvalidAuthenticationError when LinkedIn API returns 401', async () => {
nock(`${BASE_URL}/dmpSegments`)
.get(/.*/)
.query(() => true)
.reply(200, { elements: [{ id: 'dmp_segment_id' }] })
nock(`${BASE_URL}/dmpSegments/dmp_segment_id/users`).post(/.*/, updateUsersRequestBody).reply(401, {
message: 'Unauthorized',
status: 401
})

await expect(
testDestination.testAction('updateAudience', {
event,
settings: {
ad_account_id: '123',
send_email: true,
send_google_advertising_id: true
},
useDefaultMappings: true,
auth,
mapping: {
personas_audience_key: 'personas_test_audience'
}
})
).rejects.toThrow('Invalid LinkedIn OAuth access token. Please reauthenticate to retrieve a valid access token.')
})

it('should throw RetryableError when LinkedIn API returns non-200 status (e.g., 404)', async () => {
nock(`${BASE_URL}/dmpSegments`)
.get(/.*/)
.query(() => true)
.reply(200, { elements: [{ id: 'dmp_segment_id' }] })
nock(`${BASE_URL}/dmpSegments/dmp_segment_id/users`).post(/.*/, updateUsersRequestBody).reply(404, {
message: 'Not Found',
status: 404
})

await expect(
testDestination.testAction('updateAudience', {
event,
settings: {
ad_account_id: '123',
send_email: true,
send_google_advertising_id: true
},
useDefaultMappings: true,
auth,
mapping: {
personas_audience_key: 'personas_test_audience'
}
})
).rejects.toThrow('Error while attempting to update LinkedIn DMP Segment. This batch will be retried.')
})

it('should fail if `personas_audience_key` field does not match the `source_segment_id` field', async () => {
await expect(
testDestination.testAction('updateAudience', {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { StatsContext } from '@segment/actions-core'
import { RequestClient, RetryableError, IntegrationError } from '@segment/actions-core'
import { RequestClient, RetryableError, IntegrationError, InvalidAuthenticationError } from '@segment/actions-core'
import type { Settings } from '../generated-types'
import type { Payload } from './generated-types'
import { LinkedInAudiences } from '../api'
Expand Down Expand Up @@ -38,11 +38,28 @@ export async function processPayload(

const res = await linkedinApiClient.batchUpdate(dmpSegmentId, elements)

// Handle 401 explicitly so the framework can trigger token refresh.
// Without this, 401s are swallowed by throwHttpErrors: false and converted
// to RetryableError, preventing the OAuth refresh flow from executing.
if (res.status === 401) {
statsContext?.statsClient?.incr('linkedin_dmp_segment_update_error', 1, [
...statsContext?.tags,
`status_code:${res.status}`
])
throw new InvalidAuthenticationError(
'Invalid LinkedIn OAuth access token. Please reauthenticate to retrieve a valid access token.'
)
}

// At this point, if LinkedIn's API returns a 404 error, it's because the audience
// Segment just created isn't available yet for updates via this endpoint.
// Audiences are usually available to accept batches of data 1 - 2 minutes after
// they're created. Here, we'll throw an error and let Centrifuge handle the retry.
if (res.status !== 200) {
statsContext?.statsClient?.incr('linkedin_dmp_segment_update_error', 1, [
...statsContext?.tags,
`status_code:${res.status}`
])
throw new RetryableError('Error while attempting to update LinkedIn DMP Segment. This batch will be retried.')
}

Expand Down
Loading