Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Responsys: Audiences as PETs mapping #2398

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0621fd8
Initial implementation.
seg-leonelsanches Jul 26, 2024
aff152f
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Aug 20, 2024
8cb6edb
Using `testAuthentication` to fetch the auth token.
seg-leonelsanches Aug 20, 2024
17ae3f6
General improvements in Responsys Actions.
seg-leonelsanches Aug 23, 2024
ae764f6
- Better types in Responsys;
seg-leonelsanches Aug 27, 2024
ca5cf76
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Aug 27, 2024
5a68f1a
Fixes in Responsys while testing Send Audience as PET Action.
seg-leonelsanches Aug 28, 2024
df8b687
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Sep 3, 2024
e79bc3e
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Sep 5, 2024
58739e5
- Adding `computation_key`, `traits_or_props` to mapping;
seg-leonelsanches Sep 10, 2024
8f38e18
First implementation of mapping for one audience per PET in Responsys.
seg-leonelsanches Sep 13, 2024
b25dc94
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Sep 17, 2024
cb04621
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Sep 25, 2024
b2496cf
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Oct 1, 2024
961e0e1
Basic unit tests.
seg-leonelsanches Oct 1, 2024
577f4ce
Adding batch unit tests.
seg-leonelsanches Oct 2, 2024
274b2a9
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Oct 2, 2024
16f1d22
Code suggestions from PR.
seg-leonelsanches Oct 9, 2024
20475cb
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Oct 9, 2024
f5ab0e8
Code suggestions from PR.
seg-leonelsanches Oct 9, 2024
efe1485
Reversing suggestion that breaks the PR build.
seg-leonelsanches Oct 9, 2024
2ee891b
Updating testAuthentication unit test for Responsys.
seg-leonelsanches Oct 9, 2024
d8443d6
Merge remote-tracking branch 'origin/main' into responsys-audiences-a…
seg-leonelsanches Oct 21, 2024
e6e548e
Reversing `refreshAccessToken` method using `auth` parameters, back t…
seg-leonelsanches Oct 21, 2024
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
@@ -1,3 +1,5 @@
import nock from 'nock'

import { createTestIntegration } from '@segment/actions-core'
import Definition from '../index'
import { Settings } from '../generated-types'
Expand All @@ -7,11 +9,17 @@ const testDestination = createTestIntegration(Definition)
describe('Responsys', () => {
describe('testAuthentication', () => {
it('should validate settings correctly', async () => {
nock('https://instance-api.responsys.ocs.oraclecloud.com').post('/rest/api/v1.3/auth/token').reply(200, {
authToken: 'anything',
issuedAt: 1728492996097,
endPoint: 'https://cj01qwy-api.responsys.ocs.oraclecloud.com'
})

const settings: Settings = {
segmentWriteKey: 'testKey',
username: 'testUser',
userPassword: 'testPassword',
baseUrl: 'https://example.com',
baseUrl: 'https://instance-api.responsys.ocs.oraclecloud.com',
profileListName: 'TESTLIST',
insertOnNoMatch: true,
matchColumnName1: 'EMAIL_ADDRESS',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import sendCustomTraits from './sendCustomTraits'
import sendAudience from './sendAudience'
import upsertListMember from './upsertListMember'

import sendAudienceAsPet from './sendAudienceAsPet'

interface RefreshTokenResponse {
authToken: string
}
Expand Down Expand Up @@ -170,12 +172,25 @@ const destination: DestinationDefinition<Settings> = {
type: 'string'
}
},
testAuthentication: (_, { settings }) => {
if (settings.baseUrl.startsWith('https://'.toLowerCase())) {
return Promise.resolve('Success')
} else {
testAuthentication: async (request, { settings }) => {
if (!settings.baseUrl.startsWith('https://'.toLowerCase())) {
throw new IntegrationError('Responsys endpoint URL must start with https://', 'INVALID_URL', 400)
}

const baseUrl = settings.baseUrl?.replace(/\/$/, '')
const endpoint = `${baseUrl}/rest/api/v1.3/auth/token`

const res = await request<RefreshTokenResponse>(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `user_name=${encodeURIComponent(settings.username)}&password=${encodeURIComponent(
settings.userPassword
)}&auth_type=password`
})

return Promise.resolve(res.data.authToken ? true : false)
},
refreshAccessToken: async (request, { settings }) => {
const baseUrl = settings.baseUrl?.replace(/\/$/, '')
Expand Down Expand Up @@ -203,6 +218,7 @@ const destination: DestinationDefinition<Settings> = {
},
actions: {
sendAudience,
sendAudienceAsPet,
sendCustomTraits,
upsertListMember
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ActionDefinition } from '@segment/actions-core'
import type { Settings } from '../generated-types'
import type { Payload } from './generated-types'
import { enable_batching, batch_size } from '../shared_properties'
import { enable_batching, batch_size } from '../shared-properties'
import { sendCustomTraits, getUserDataFieldNames, validateCustomTraits, validateListMemberPayload } from '../utils'
import { Data } from '../types'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import nock from 'nock'
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
import Destination from '../../index'
import { SegmentEvent } from '@segment/actions-core/*'

const testDestination = createTestIntegration(Destination)
const timestamp = new Date('Thu Jun 10 2024 11:08:04 GMT-0700 (Pacific Daylight Time)').toISOString()
const responsysHost = 'https://123456-api.responsys.ocs.oraclecloud.com'
const profileListName = 'TEST_PROFILE_LIST'
const folderName = 'TEST_FOLDER'

describe('Responsys.sendAudienceAsPet', () => {
describe('Successful scenarios', () => {
describe('Single event', () => {
const audienceKey = 'looney_tunes_audience'
const event = createTestEvent({
timestamp,
type: 'identify',
context: {
personas: {
computation_key: audienceKey
}
},
traits: {
email: '[email protected]',
firstName: 'Daffy',
lastName: 'Duck'
},
userId: '12345'
})

const actionPayload = {
event,
mapping: {
folder_name: folderName,
pet_name: {
'@path': '$.context.personas.computation_key'
},
userData: {
EMAIL_ADDRESS_: {
'@path': '$.traits.email'
},
CUSTOMER_ID_: {
'@path': '$.userId'
}
}
},
useDefaultMappings: true,
settings: {
baseUrl: responsysHost,
username: 'abcd',
userPassword: 'abcd',
insertOnNoMatch: false,
matchColumnName1: 'EMAIL_ADDRESS_',
updateOnMatch: 'REPLACE_ALL',
defaultPermissionStatus: 'OPTOUT',
profileListName
}
}

it('sends an event with default mappings + default settings, PET does not exist yet', async () => {
nock(responsysHost).get(`/rest/api/v1.3/lists/${profileListName}/listExtensions`).reply(200, [])

nock(responsysHost)
.post(`/rest/api/v1.3/lists/${profileListName}/listExtensions`)
.reply(200, { results: [{}] })

nock(responsysHost)
.post(`/rest/asyncApi/v1.3/lists/${profileListName}/members`)
.reply(200, { results: [{}] })

nock(responsysHost)
.post(`/rest/api/v1.3/lists/${profileListName}/listExtensions/${audienceKey}/members`)
.reply(200, { results: [{}] })

const responses = await testDestination.testAction('sendAudienceAsPet', actionPayload)

expect(responses.length).toBe(4)
expect(responses[0].status).toBe(200)
expect(responses[1].status).toBe(200)
expect(responses[2].status).toBe(200)
expect(responses[3].status).toBe(200)
})

it('sends an event with default mappings + default settings, PET exists', async () => {
nock(responsysHost)
.get(`/rest/api/v1.3/lists/${profileListName}/listExtensions`)
.reply(200, [
{
profileExtension: { objectName: audienceKey }
}
])

nock(responsysHost)
.post(`/rest/asyncApi/v1.3/lists/${profileListName}/members`)
.reply(200, { results: [{}] })

nock(responsysHost)
.post(`/rest/api/v1.3/lists/${profileListName}/listExtensions/${audienceKey}/members`)
.reply(200, { results: [{}] })

const responses = await testDestination.testAction('sendAudienceAsPet', actionPayload)

expect(responses.length).toBe(3)
expect(responses[0].status).toBe(200)
expect(responses[1].status).toBe(200)
expect(responses[2].status).toBe(200)
})
})

describe('Batch', () => {
const events: SegmentEvent[] = [
{
timestamp,
type: 'identify',
context: {
personas: {
computation_key: 'looney_tunes_audience'
}
},
traits: {
email: '[email protected]'
},
anonymousId: 'abcdef-abcd-1234-1234-1234'
},
{
timestamp,
type: 'identify',
context: {
personas: {
computation_key: 'looney_tunes_audience'
}
},
traits: {
email: '[email protected]'
},
userId: '12345'
},
{
timestamp,
type: 'identify',
context: {
personas: {
computation_key: 'looney_tunes_audience'
}
},
traits: {
riid: '123456'
},
anonymousId: 'abcdef-abcd-2345-1234-3456'
}
]

it('sends events with different match keys', async () => {
const actionPayload = {
events,
mapping: {
folder_name: folderName,
pet_name: {
'@path': '$.context.personas.computation_key'
},
computation_key: {
'@path': '$.context.personas.computation_key'
},
traits_or_props: {
'@path': '$.traits'
},
userData: {
EMAIL_ADDRESS_: {
'@path': '$.traits.email'
},
CUSTOMER_ID_: {
'@path': '$.userId'
},
RIID_: {
'@path': '$.traits.riid'
}
}
},
settings: {
baseUrl: responsysHost,
username: 'abcd',
userPassword: 'abcd',
insertOnNoMatch: false,
matchColumnName1: 'EMAIL_ADDRESS_',
updateOnMatch: 'REPLACE_ALL',
defaultPermissionStatus: 'OPTOUT',
profileListName
}
}

nock(responsysHost).get(`/rest/api/v1.3/lists/${profileListName}/listExtensions`).reply(200, [])

nock(responsysHost)
.post(`/rest/api/v1.3/lists/${profileListName}/listExtensions`)
.reply(200, { results: [{}] })

nock(responsysHost)
.post(`/rest/asyncApi/v1.3/lists/${profileListName}/members`)
.reply(200, { results: [{}] })

nock(responsysHost)
.post(`/rest/api/v1.3/lists/${profileListName}/listExtensions/looney_tunes_audience/members`)
.times(3)
.reply(200, { results: [{}] })

/* for (const event of events) {
;(event.context as any).personas.audience_settings = {
computation_key: 'looney_tunes_audience',
external_audience_id: '12345'
}
} */

const responses = await testDestination.executeBatch('sendAudienceAsPet', actionPayload)

expect(responses.length).toBe(3)
expect(responses[0].status).toBe(200)
expect(responses[1].status).toBe(200)
expect(responses[2].status).toBe(200)
})
})
})
})
Loading
Loading