Skip to content

Commit

Permalink
Stackadapt (ITE-146) Update Segment Engage Destination to handle onDe…
Browse files Browse the repository at this point in the history
…lete events (segmentio#2599)

* Added OnDelete

* Fix(ITE-146) Rethrow Error
  • Loading branch information
illumin04 authored Nov 26, 2024
1 parent b1c9e56 commit 2d219c9
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { createTestEvent, createTestIntegration } from '@segment/actions-core'
import nock from 'nock'
import Definition from '../index'
import { GQL_ENDPOINT } from '../functions'

const testDestination = createTestIntegration(Definition)

describe('StackAdapt Audiences - Destination Tests', () => {
const mockSettings = { apiKey: 'test-api-key' }
const gqlHostUrl = 'https://api.stackadapt.com'

afterEach(() => {
nock.cleanAll()
})

describe('testAuthentication', () => {
it('should validate authentication inputs', async () => {
nock(GQL_ENDPOINT, {
reqheaders: {
authorization: `Bearer ${mockSettings.apiKey}`,
'content-type': 'application/json'
}
})
.post('', {
query: /tokenInfo/
})
.reply(200, {
data: {
tokenInfo: {
scopesByAdvertiser: {
nodes: [
{
advertiser: { name: 'Test Advertiser' },
scopes: ['WRITE']
}
]
}
}
}
})

await expect(testDestination.testAuthentication(mockSettings)).resolves.not.toThrowError()
})

it('should fail if authentication is invalid', async () => {
nock(GQL_ENDPOINT).post('').reply(403, {})

await expect(testDestination.testAuthentication(mockSettings)).rejects.toThrowError('403Forbidden')
})
})

describe('onDelete', () => {
it('should delete a user with a given userId', async () => {
const userId = '9999'
const event = createTestEvent({ userId, type: 'identify' })

// Mock the GraphQL deleteProfilesWithExternalIds mutation
nock(gqlHostUrl)
.post('/graphql', (body) => {
return body.query.includes('deleteProfilesWithExternalIds') && body.query.includes(userId)
})
.reply(200, {
data: { deleteProfilesWithExternalIds: { userErrors: [] } }
})

const response = await testDestination.onDelete!(event, {
apiKey: 'test-api-key'
})

expect(response).toMatchObject({
data: { deleteProfilesWithExternalIds: { userErrors: [] } }
})
})

it('should throw an error if profile deletion fails with userErrors', async () => {
const userId = '9999'
const event = createTestEvent({ userId, type: 'identify' })

// Mock the GraphQL deleteProfilesWithExternalIds mutation with an error
nock(gqlHostUrl)
.post('/graphql')
.reply(200, {
data: {
deleteProfilesWithExternalIds: {
userErrors: [{ message: 'Deletion failed' }]
}
}
})

await expect(
testDestination.onDelete!(event, {
apiKey: 'test-api-key'
})
).rejects.toThrowError('Profile deletion was not successful: Deletion failed')
})
})
})
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { DestinationDefinition } from '@segment/actions-core'
import type { Settings } from './generated-types'

import { IntegrationError } from '@segment/actions-core'
import forwardProfile from './forwardProfile'
import forwardAudienceEvent from './forwardAudienceEvent'
import { AdvertiserScopesResponse } from './types'
import { GQL_ENDPOINT } from './functions'
import { GQL_ENDPOINT, EXTERNAL_PROVIDER, sha256hash } from './functions'

const destination: DestinationDefinition<Settings> = {
name: 'StackAdapt Audiences',
Expand Down Expand Up @@ -59,6 +59,45 @@ const destination: DestinationDefinition<Settings> = {
}
}
},
onDelete: async (request, { payload }) => {
const userId = payload.userId
const formattedExternalIds = `["${userId}"]`
const syncId = sha256hash(String(userId))

const mutation = `mutation {
deleteProfilesWithExternalIds(
externalIds: ${formattedExternalIds},
externalProvider: "${EXTERNAL_PROVIDER}",
syncId: "${syncId}"
) {
userErrors {
message
path
}
}
}`

const response = await request(GQL_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: mutation })
})

const result: {
data: {
deleteProfilesWithExternalIds: {
userErrors: { message: string }[]
}
}
} = await response.json()

if (result.data.deleteProfilesWithExternalIds.userErrors.length > 0) {
const errorMessages = result.data.deleteProfilesWithExternalIds.userErrors.map((e) => e.message).join(', ')
throw new IntegrationError(`Profile deletion was not successful: ${errorMessages}`, 'DELETE_FAILED', 400)
}
return result
},

actions: {
forwardProfile,
forwardAudienceEvent
Expand Down

0 comments on commit 2d219c9

Please sign in to comment.