Skip to content

Commit

Permalink
[Marketo] - Add MultiStatus Support (#2653)
Browse files Browse the repository at this point in the history
* Multistatus WIP

* WIP

* Added batch code path with multistatus

* Update imports

Co-authored-by: Varadarajan V <[email protected]>

* Fix refresh token issue

* Revert "Fix refresh token issue"

This reverts commit 364322e.

* Updated refresh access token handler

* Added unit tests

* addressed review comments

* Revert throwhttperror for non-batched events

---------

Co-authored-by: Varadarajan V <[email protected]>
  • Loading branch information
sayan-das-in and varadarajan-tw authored Jan 21, 2025
1 parent 2869f83 commit f3c003f
Show file tree
Hide file tree
Showing 4 changed files with 568 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
import { SegmentEvent, createTestEvent, createTestIntegration } from '@segment/actions-core'
import nock from 'nock'
import MarketoStaticList from '../index'

beforeEach(() => nock.cleanAll())

const testDestination = createTestIntegration(MarketoStaticList)

const settings = {
folder_name: 'Test',
api_endpoint: 'https://000-ABC-123.mktorest.com',
client_id: 'abc00000-1234-56a7-890f-a12b4456c789',
client_secret: 'client-secret'
}

describe('MultiStatus', () => {
describe('addToList', () => {
const mapping = {
event_name: {
'@path': '$.event'
},
external_id: {
'@path': '$.context.personas.external_audience_id'
},
lookup_field: 'email',
data: {
email: {
'@if': {
exists: {
'@path': '$.context.traits.email'
},
then: {
'@path': '$.context.traits.email'
},
else: {
'@path': '$.properties.email'
}
}
}
},
enable_batching: true,
batch_size: 100
}

const events: SegmentEvent[] = [
createTestEvent({
type: 'track',
event: 'Audience Entered',
anonymousId: '04618777717597463653257185356814720321',
messageId: 'api-2PglAN7SNoZT2a0A70Yqxu0pSpW',
properties: {
email: '[email protected]'
},
context: {
personas: {
external_audience_id: '101'
}
},
receivedAt: '2023-05-12T10:14:19.750Z',
timestamp: '2023-05-12T10:14:06.946Z',
userId: '0001'
}),
createTestEvent({
type: 'track',
event: 'Audience Entered',
anonymousId: '04618777717597463653257185356814720321',
messageId: 'api-2PglAN7SNoZT2a0A70Yqxu0pSpW',
properties: {
email: '[email protected]'
},
context: {
personas: {
external_audience_id: '101'
}
},
receivedAt: '2023-05-12T10:14:19.750Z',
timestamp: '2023-05-12T10:14:06.946Z',
userId: '0002'
})
]

it('should throw error for invalid access token', async () => {
nock(settings.api_endpoint)
.post('/bulk/v1/leads.json?format=csv&listId=101&lookupField=email')
.reply(200, {
requestId: '0001#1234f2f3e4',
success: false,
warnings: [],
errors: [
{
code: '601',
message: 'Access token invalid'
}
]
})

const response = testDestination.executeBatch('addToList', {
events,
settings,
mapping
})

await expect(response).rejects.toThrowError('Access token invalid')
})

it('should return multistatus for any other error', async () => {
nock(settings.api_endpoint)
.post('/bulk/v1/leads.json?format=csv&listId=101&lookupField=email')
.reply(200, {
requestId: '0001#1234f2f3e4',
success: false,
warnings: [],
errors: [
{
code: '612',
message: 'Invalid Content Type'
}
]
})

const response = await testDestination.executeBatch('addToList', {
events,
settings,
mapping
})

expect(response).toMatchObject([
{
status: 400,
errortype: 'NOT_ACCEPTABLE',
errormessage: 'Invalid Content Type',
errorreporter: 'INTEGRATIONS'
},
{
status: 400,
errortype: 'NOT_ACCEPTABLE',
errormessage: 'Invalid Content Type',
errorreporter: 'INTEGRATIONS'
}
])
})

it('should return multistatus for success', async () => {
nock(settings.api_endpoint).post('/bulk/v1/leads.json?format=csv&listId=101&lookupField=email').reply(200, {
requestId: '0001#1234f2f3e4',
success: true,
warnings: [],
errors: []
})

const response = await testDestination.executeBatch('addToList', {
events,
settings,
mapping
})

expect(response[0]).toMatchObject({
status: 200,
body: {
requestId: '0001#1234f2f3e4',
success: true,
warnings: [],
errors: []
},
sent: '[email protected]'
})

expect(response[1]).toMatchObject({
status: 200,
body: {
requestId: '0001#1234f2f3e4',
success: true,
warnings: [],
errors: []
},
sent: '[email protected]'
})
})
})

describe('removeFromList', () => {
const mapping = {
event_name: {
'@path': '$.event'
},
external_id: {
'@path': '$.context.personas.external_audience_id'
},
lookup_field: 'email',
field_value: {
'@if': {
exists: { '@path': '$.context.traits.email' },
then: { '@path': '$.context.traits.email' },
else: { '@path': '$.properties.email' }
}
},
enable_batching: true,
batch_size: 100
}

const events: SegmentEvent[] = [
createTestEvent({
type: 'track',
event: 'Audience Entered',
anonymousId: '04618777717597463653257185356814720321',
messageId: 'api-2PglAN7SNoZT2a0A70Yqxu0pSpW',
properties: {
email: '[email protected]'
},
context: {
personas: {
external_audience_id: '101'
}
},
receivedAt: '2023-05-12T10:14:19.750Z',
timestamp: '2023-05-12T10:14:06.946Z',
userId: '0001'
}),
createTestEvent({
type: 'track',
event: 'Audience Entered',
anonymousId: '04618777717597463653257185356814720321',
messageId: 'api-2PglAN7SNoZT2a0A70Yqxu0pSpW',
properties: {
email: '[email protected]'
},
context: {
personas: {
external_audience_id: '101'
}
},
receivedAt: '2023-05-12T10:14:19.750Z',
timestamp: '2023-05-12T10:14:06.946Z',
userId: '0002'
})
]

it('should throw error for invalid access token', async () => {
nock(settings.api_endpoint)
.get('/rest/v1/leads.json?filterType=email&filterValues=test1%40example.org%2Ctest2%40example.org')
.reply(200, {
requestId: '0001#1234f2f3e4',
success: false,
warnings: [],
errors: [
{
code: '601',
message: 'Access token invalid'
}
]
})

const response = testDestination.executeBatch('removeFromList', {
events,
settings,
mapping
})

await expect(response).rejects.toThrowError('Access token invalid')
})

it('should return multistatus for any other error', async () => {
nock(settings.api_endpoint)
.get('/rest/v1/leads.json?filterType=email&filterValues=test1%40example.org%2Ctest2%40example.org')
.reply(200, {
requestId: '0001#1234f2f3e4',
success: false,
warnings: [],
errors: [
{
code: '612',
message: 'Invalid Content Type'
}
]
})

const response = await testDestination.executeBatch('removeFromList', {
events,
settings,
mapping
})

expect(response).toMatchObject([
{
status: 400,
errortype: 'NOT_ACCEPTABLE',
errormessage: 'Invalid Content Type',
errorreporter: 'INTEGRATIONS'
},
{
status: 400,
errortype: 'NOT_ACCEPTABLE',
errormessage: 'Invalid Content Type',
errorreporter: 'INTEGRATIONS'
}
])
})

it('should return multistatus for success', async () => {
nock(settings.api_endpoint)
.get('/rest/v1/leads.json?filterType=email&filterValues=test1%40example.org%2Ctest2%40example.org')
.reply(200, {
requestId: '0001#1234f2f3e4',
success: true,
warnings: [],
errors: [],
result: [
{
id: 1
},
{
id: 2
}
]
})

nock(settings.api_endpoint).delete('/rest/v1/lists/101/leads.json?id=1,2').reply(200, {
requestId: '0001#1234f2f3e4',
success: true,
warnings: [],
errors: []
})

const response = await testDestination.executeBatch('removeFromList', {
events,
settings,
mapping
})

expect(response[0]).toMatchObject({
status: 200,
body: {
requestId: '0001#1234f2f3e4',
success: true,
warnings: [],
errors: []
},
sent: 'id=1'
})

expect(response[1]).toMatchObject({
status: 200,
body: {
requestId: '0001#1234f2f3e4',
success: true,
warnings: [],
errors: []
},
sent: 'id=2'
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { IntegrationError, ActionDefinition } from '@segment/actions-core'
import type { Settings } from '../generated-types'
import type { Payload } from './generated-types'
import { external_id, lookup_field, data, enable_batching, batch_size, event_name } from '../properties'
import { addToList, createList, getList } from '../functions'
import { addToList, addToListBatch, createList, getList } from '../functions'

const action: ActionDefinition<Settings, Payload> = {
title: 'Add to List',
Expand Down Expand Up @@ -94,11 +94,11 @@ const action: ActionDefinition<Settings, Payload> = {
},
perform: async (request, { settings, payload, statsContext, hookOutputs }) => {
statsContext?.statsClient?.incr('addToAudience', 1, statsContext?.tags)
return addToList(request, settings, [payload], statsContext, hookOutputs?.retlOnMappingSave?.outputs)
return addToList(request, settings, payload, statsContext, hookOutputs?.retlOnMappingSave?.outputs)
},
performBatch: async (request, { settings, payload, statsContext, hookOutputs }) => {
statsContext?.statsClient?.incr('addToAudience.batch', 1, statsContext?.tags)
return addToList(request, settings, payload, statsContext, hookOutputs?.retlOnMappingSave?.outputs)
return addToListBatch(request, settings, payload, statsContext, hookOutputs?.retlOnMappingSave?.outputs)
}
}

Expand Down
Loading

0 comments on commit f3c003f

Please sign in to comment.