Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c303690
getting started on audience membership
joe-ayoub-segment Mar 10, 2026
7992933
adding retlMembership
joe-ayoub-segment Mar 10, 2026
0a65bae
updating payload objects with audience_membership
joe-ayoub-segment Mar 10, 2026
3e7cc58
moving logic
joe-ayoub-segment Mar 10, 2026
c2f52f6
moving membership out of payload
joe-ayoub-segment Mar 11, 2026
127121f
update
joe-ayoub-segment Mar 11, 2026
e5ce255
updates
joe-ayoub-segment Mar 11, 2026
6e308a1
update
joe-ayoub-segment Mar 11, 2026
3141486
migrated amplitude cohorts
joe-ayoub-segment Mar 11, 2026
42f5987
type fix
joe-ayoub-segment Mar 11, 2026
b0add83
updating tests
joe-ayoub-segment Mar 12, 2026
a7d2a8b
adding flag
joe-ayoub-segment Mar 12, 2026
ab36b5a
fixing tests for amplitude cohorts
joe-ayoub-segment Mar 12, 2026
25a89cd
fixing test authentication
joe-ayoub-segment Mar 12, 2026
acf98ef
fixing amplitude cohort test
joe-ayoub-segment Mar 12, 2026
6f1f38c
adding flags to core
joe-ayoub-segment Mar 13, 2026
29f4186
adding google enhanced conversions
joe-ayoub-segment Mar 13, 2026
8c533df
adding facebook custom audiences
joe-ayoub-segment Mar 13, 2026
f86ee88
errors and flags update
joe-ayoub-segment Mar 13, 2026
0242bc7
braze cohorts audienceMembership migration
joe-ayoub-segment Mar 14, 2026
a89a42b
braze cohorts core complete apart from tests
joe-ayoub-segment Mar 14, 2026
616d193
adding tests for flags = true
joe-ayoub-segment Mar 14, 2026
ca04e80
removing snapshot
joe-ayoub-segment Mar 14, 2026
87a57e0
adding snapshot
joe-ayoub-segment Mar 14, 2026
ec0a6be
upating snapshot
joe-ayoub-segment Mar 14, 2026
2c76e47
linkedin audiences using audienceMembership from Core
joe-ayoub-segment Mar 14, 2026
b82ddbe
removing core flag
joe-ayoub-segment Mar 17, 2026
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
346 changes: 346 additions & 0 deletions packages/core/src/__tests__/audience-membership.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
import { createTestIntegration } from '../create-test-integration'
import { engageAudienceMembership, retlAudienceMembership } from '../audience-membership'
import { DestinationDefinition } from '../destination-kit'
import { ExecuteInput } from '../destination-kit/types'
import { JSONObject } from '../json-object'

describe('engageAudienceMembership', () => {
describe('identify events', () => {
it('returns true when type is identify and traits[computation_key] is true', () => {
expect(
engageAudienceMembership({
type: 'identify',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
traits: { my_audience: true }
})
).toBe(true)
})

it('returns false when type is identify and traits[computation_key] is false', () => {
expect(
engageAudienceMembership({
type: 'identify',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
traits: { my_audience: false }
})
).toBe(false)
})

it('returns undefined when type is identify but computation_class is missing', () => {
expect(
engageAudienceMembership({
type: 'identify',
context: { personas: { computation_key: 'my_audience' } },
traits: { my_audience: true }
})
).toBeUndefined()
})

it('returns undefined when type is identify but computation_class is not audience or journey_step', () => {
expect(
engageAudienceMembership({
type: 'identify',
context: { personas: { computation_class: 'computed_trait', computation_key: 'my_audience' } },
traits: { my_audience: true }
})
).toBeUndefined()
})

it('returns undefined when type is identify but computation_key is missing', () => {
expect(
engageAudienceMembership({
type: 'identify',
context: { personas: { computation_class: 'audience' } },
traits: { my_audience: true }
})
).toBeUndefined()
})

it('returns undefined when type is identify but traits[computation_key] is absent', () => {
expect(
engageAudienceMembership({
type: 'identify',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
traits: {}
})
).toBeUndefined()
})

it('returns undefined when type is identify but traits[computation_key] is not a boolean', () => {
expect(
engageAudienceMembership({
type: 'identify',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
traits: { my_audience: 'true' }
})
).toBeUndefined()
})
})

describe('track events', () => {
it('returns true when type is track and properties[computation_key] is true', () => {
expect(
engageAudienceMembership({
type: 'track',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
properties: { my_audience: true }
})
).toBe(true)
})

it('returns false when type is track and properties[computation_key] is false', () => {
expect(
engageAudienceMembership({
type: 'track',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
properties: { my_audience: false }
})
).toBe(false)
})

it('returns undefined when type is track but computation_class is missing', () => {
expect(
engageAudienceMembership({
type: 'track',
context: { personas: { computation_key: 'my_audience' } },
properties: { my_audience: true }
})
).toBeUndefined()
})

it('returns undefined when type is track but computation_class is not audience or journey_step', () => {
expect(
engageAudienceMembership({
type: 'track',
context: { personas: { computation_class: 'computed_trait', computation_key: 'my_audience' } },
properties: { my_audience: true }
})
).toBeUndefined()
})

it('returns undefined when type is track but computation_key is missing', () => {
expect(
engageAudienceMembership({
type: 'track',
context: { personas: { computation_class: 'audience' } },
properties: { my_audience: true }
})
).toBeUndefined()
})

it('returns undefined when type is track but properties[computation_key] is absent', () => {
expect(
engageAudienceMembership({
type: 'track',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
properties: {}
})
).toBeUndefined()
})

it('returns undefined when type is track but properties[computation_key] is not a boolean', () => {
expect(
engageAudienceMembership({
type: 'track',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
properties: { my_audience: 'true' }
})
).toBeUndefined()
})
})

describe('other', () => {
it('returns true when computation_class is journey_step (identify)', () => {
expect(
engageAudienceMembership({
type: 'identify',
context: { personas: { computation_class: 'journey_step', computation_key: 'my_step' } },
traits: { my_step: true }
})
).toBe(true)
})

it('returns undefined when rawData is undefined', () => {
expect(engageAudienceMembership(undefined)).toBeUndefined()
})
})
})

describe('retlAudienceMembership', () => {
describe('returns true (added to audience)', () => {
it('returns true when syncMode is add and event is new', () => {
expect(retlAudienceMembership({ type: 'track', event: 'new' }, 'add')).toBe(true)
})

it('returns true when syncMode is update and event is updated', () => {
expect(retlAudienceMembership({ type: 'track', event: 'updated' }, 'update')).toBe(true)
})

it('returns true when syncMode is upsert and event is new', () => {
expect(retlAudienceMembership({ type: 'track', event: 'new' }, 'upsert')).toBe(true)
})

it('returns true when syncMode is upsert and event is updated', () => {
expect(retlAudienceMembership({ type: 'track', event: 'updated' }, 'upsert')).toBe(true)
})

it('returns true when syncMode is mirror and event is new', () => {
expect(retlAudienceMembership({ type: 'track', event: 'new' }, 'mirror')).toBe(true)
})

it('returns true when syncMode is mirror and event is updated', () => {
expect(retlAudienceMembership({ type: 'track', event: 'updated' }, 'mirror')).toBe(true)
})
})

describe('returns false (removed from audience)', () => {
it('returns false when syncMode is delete and event is deleted', () => {
expect(retlAudienceMembership({ type: 'track', event: 'deleted' }, 'delete')).toBe(false)
})

it('returns false when syncMode is mirror and event is deleted', () => {
expect(retlAudienceMembership({ type: 'track', event: 'deleted' }, 'mirror')).toBe(false)
})
})

describe('failure cases', () => {
it('returns undefined when rawData is undefined', () => {
expect(retlAudienceMembership(undefined, 'add')).toBeUndefined()
})

it('returns undefined when syncMode is missing', () => {
expect(retlAudienceMembership({ type: 'track', event: 'new' })).toBeUndefined()
})

it('returns undefined when event type is not track', () => {
expect(retlAudienceMembership({ type: 'identify', event: 'new' }, 'add')).toBeUndefined()
})

it('returns undefined when syncMode is add but event is not new', () => {
expect(retlAudienceMembership({ type: 'track', event: 'updated' }, 'add')).toBeUndefined()
})

it('returns undefined when syncMode is delete but event is not deleted', () => {
expect(retlAudienceMembership({ type: 'track', event: 'new' }, 'delete')).toBeUndefined()
})
})
})

function makeDestination(
captureRef: { data?: ExecuteInput<JSONObject, JSONObject> }
): DestinationDefinition<JSONObject> {
return {
name: 'Test Destination',
mode: 'cloud',
authentication: { scheme: 'custom', fields: {} },
actions: {
testAction: {
title: 'Test Action',
description: 'Test',
fields: {
userId: { label: 'User ID', description: 'The user ID', type: 'string' }
},
syncMode: {
default: 'add',
label: 'Sync Mode',
description: 'The sync mode',
choices: [
{ label: 'Add', value: 'add' },
{ label: 'Update', value: 'update' },
{ label: 'Upsert', value: 'upsert' },
{ label: 'Delete', value: 'delete' },
{ label: 'Mirror', value: 'mirror' }
]
},
perform: (_request, data) => {
captureRef.data = data as ExecuteInput<JSONObject, JSONObject>
}
}
}
}
}

async function runAction(
event: object,
mapping: JSONObject = { userId: { '@path': '$.userId' } },
features: Record<string, boolean> = {}
): Promise<ExecuteInput<JSONObject, JSONObject> | undefined> {
const captureRef: { data?: ExecuteInput<JSONObject, JSONObject> } = {}
const testDestination = createTestIntegration(makeDestination(captureRef))
await testDestination.testAction('testAction', { mapping, event, features })
return captureRef.data
}

describe('audienceMembership on ExecuteInput in perform()', () => {

describe('Engage payloads', () => {
it('is true for identify event with membership in traits', async () => {
const data = await runAction({
type: 'identify',
userId: 'user-1',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
traits: { my_audience: true }
}, undefined)
expect(data?.audienceMembership).toBe(true)
})

it('is false for identify event with membership in traits', async () => {
const data = await runAction({
type: 'identify',
userId: 'user-1',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
traits: { my_audience: false }
}, undefined)
expect(data?.audienceMembership).toBe(false)
})

it('is true for track event with membership in properties', async () => {
const data = await runAction({
type: 'track',
userId: 'user-1',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
properties: { my_audience: true }
}, undefined)
expect(data?.audienceMembership).toBe(true)
})

it('is false for track event with membership in properties', async () => {
const data = await runAction({
type: 'track',
userId: 'user-1',
context: { personas: { computation_class: 'audience', computation_key: 'my_audience' } },
properties: { my_audience: false }
}, undefined)
expect(data?.audienceMembership).toBe(false)
})
})

describe('RETL payloads', () => {
it('is true for track event with syncMode add and event name new', async () => {
const data = await runAction(
{ type: 'track', userId: 'user-1', event: 'new' },
{ userId: { '@path': '$.userId' }, __segment_internal_sync_mode: 'add' }
)
expect(data?.audienceMembership).toBe(true)
})

it('is false for track event with syncMode delete and event name deleted', async () => {
const data = await runAction(
{ type: 'track', userId: 'user-1', event: 'deleted' },
{ userId: { '@path': '$.userId' }, __segment_internal_sync_mode: 'delete' }
)
expect(data?.audienceMembership).toBe(false)
})
})

describe('non-audience events', () => {
it('is undefined for a non-audience event', async () => {
const data = await runAction({
type: 'track',
userId: 'user-1',
properties: { foo: 'bar' }
}, undefined)
expect(data?.audienceMembership).toBeUndefined()
})
})
})
Loading
Loading