Skip to content
Open
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 @@ -13,7 +13,7 @@ Array [
},
"occurred": false,
"user": Object {
"named_user_id": "Z8PnKI8*YgWA",
"channel": "Z8PnKI8*YgWA",
},
},
]
Expand All @@ -31,7 +31,7 @@ Array [
},
"occurred": false,
"user": Object {
"named_user_id": "Z8PnKI8*YgWA",
"channel": "Z8PnKI8*YgWA",
},
},
]
Expand All @@ -40,15 +40,15 @@ Array [
exports[`Testing snapshot for actions-airship destination: manageTags action - all fields 1`] = `
Object {
"audience": Object {
"named_user_id": "XvmIpuaw",
"channel": "XvmIpuaw",
},
}
`;

exports[`Testing snapshot for actions-airship destination: manageTags action - required fields 1`] = `
Object {
"audience": Object {
"named_user_id": "XvmIpuaw",
"channel": "XvmIpuaw",
},
}
`;
Expand Down Expand Up @@ -207,7 +207,7 @@ Object {
},
],
"audience": Object {
"named_user_id": "*N75*",
"channel": "*N75*",
},
}
`;
Expand All @@ -216,7 +216,7 @@ exports[`Testing snapshot for actions-airship destination: setAttributes action
Object {
"attributes": Array [],
"audience": Object {
"named_user_id": "*N75*",
"channel": "*N75*",
},
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,30 @@ import { DecoratedResponse } from '@segment/actions-core'
const testDestination = createTestIntegration(Airship)

describe('Airship', () => {
describe('extendRequest headers', () => {
it('should set the Segment attribution User-Agent header with the app key', async () => {
const now = new Date().toISOString()
const event = createTestEvent({
userId: 'test-user-rzoj4u7gqw',
timestamp: now,
traits: { trait1: 1 }
})

nock('https://go.urbanairship.com').post('/api/channels/attributes').reply(200, {})

const responses = await testDestination.testAction('setAttributes', {
event,
useDefaultMappings: true,
settings: {
access_token: 'foo',
app_key: 'bar',
endpoint: 'US'
}
})
expect(responses[0].options.headers?.get('user-agent')).toBe('PartnerIntegrations/Segment (bar)')
})
})

describe('setAttribute', () => {
it('should work for US', async () => {
const now = new Date().toISOString()
Expand Down Expand Up @@ -98,6 +122,46 @@ describe('Airship', () => {
expect(responses[0].status).toBe(200)
expect(responses[0].data).toMatchObject({})
})

it('should batch events, building each user object independently', async () => {
const now = new Date().toISOString()

nock('https://go.urbanairship.com').post('/api/custom-events').reply(200, {})

const responses = await testDestination.testBatchAction('customEvents', {
settings: {
access_token: 'foo',
app_key: 'bar',
endpoint: 'US'
},
events: [
createTestEvent({ type: 'track', timestamp: now, event: 'Event A', userId: 'named-user-1' }),
createTestEvent({
type: 'track',
timestamp: now,
event: 'Event B',
userId: undefined,
properties: { channel_id: 'chan-xyz' },
context: { device: { type: 'ios' } }
})
],
mapping: {
named_user_id: { '@path': '$.userId' },
channel_id: { '@path': '$.properties.channel_id' },
channel_type: { '@path': '$.context.device.type' },
name: { '@path': '$.event' },
occurred: { '@path': '$.timestamp' },
enable_batching: true
}
})

expect(responses[0].status).toBe(200)
const sent = JSON.parse(responses[0].options.body as string)
expect(sent).toHaveLength(2)
// Each event resolves its own audience: first a named user, second an iOS channel
expect(sent[0].user).toEqual({ named_user_id: 'named-user-1' })
expect(sent[1].user).toEqual({ ios_channel: 'chan-xyz' })
})
})

describe('manageTags', () => {
Expand Down Expand Up @@ -129,6 +193,72 @@ describe('Airship', () => {
})
})

describe('setAttribute with channel_id', () => {
it('should use channel audience', async () => {
const now = new Date().toISOString()
const event = createTestEvent({
timestamp: now,
traits: { trait1: 1 }
})

nock('https://go.urbanairship.com').post('/api/channels/attributes').reply(200, {})

const responses = await testDestination.testAction('setAttributes', {
event,
settings: { access_token: 'foo', app_key: 'bar', endpoint: 'US' },
mapping: {
channel_id: 'chan-abc',
channel_type: 'email',
occurred: now,
attributes: { trait1: 1 }
}
})
expect(responses[0].status).toBe(200)
})
})

describe('customEvents with channel_id', () => {
it('should use channel audience', async () => {
const now = new Date().toISOString()
const event = createTestEvent({ type: 'track', timestamp: now })

nock('https://go.urbanairship.com').post('/api/custom-events').reply(200, {})

const responses = await testDestination.testAction('customEvents', {
event,
settings: { access_token: 'foo', app_key: 'bar', endpoint: 'US' },
mapping: {
channel_id: 'chan-abc',
channel_type: 'email',
name: 'Test Event',
occurred: now,
enable_batching: false
}
})
expect(responses[0].status).toBe(200)
})
})

describe('manageTags with channel_id', () => {
it('should use channel audience', async () => {
const event = createTestEvent({ traits: { airship_tags: { tag1: true } } })

nock('https://go.urbanairship.com').post('/api/channels/tags').reply(200, {})

const responses = await testDestination.testAction('manageTags', {
event,
settings: { access_token: 'foo', app_key: 'bar', endpoint: 'US' },
mapping: {
channel_id: 'chan-abc',
channel_type: 'sms',
tags: { tag1: true },
tag_group: 'segment-integration'
}
})
expect(responses[0].status).toBe(200)
})
})

describe('delete', () => {
it('should support deletes', async () => {
const event = createTestEvent({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,63 @@ describe('Testing _build_attribute_object', () => {
})
})

describe('Testing _build_attribute_object with JSON (array/object) values', () => {
it('should append #default to array attribute keys without an instance ID', () => {
const payload: AttributesPayload = {
named_user_id: 'test-user',
occurred: occurred.toISOString(),
attributes: {
favourite_teams: ['Team A', 'Team B', 'Team C']
}
}
const result = _private._build_attributes_object(payload)
const attr = result.find((a: any) => a.key === 'favourite_teams#default')
expect(attr?.action).toBe('set')
expect(attr?.value).toEqual(['Team A', 'Team B', 'Team C'])
})

it('should append #default to object attribute keys without an instance ID', () => {
const payload: AttributesPayload = {
named_user_id: 'test-user',
occurred: occurred.toISOString(),
attributes: {
preferences: { theme: 'dark', language: 'en' }
}
}
const result = _private._build_attributes_object(payload)
const attr = result.find((a: any) => a.key === 'preferences#default')
expect(attr?.action).toBe('set')
expect(attr?.value).toEqual({ theme: 'dark', language: 'en' })
})

it('should preserve a user-supplied instance ID in the key', () => {
const payload: AttributesPayload = {
named_user_id: 'test-user',
occurred: occurred.toISOString(),
attributes: {
'reservation#a001': { flight: 'UA123', seat: '12A' }
}
}
const result = _private._build_attributes_object(payload)
const attr = result.find((a: any) => a.key === 'reservation#a001')
expect(attr?.action).toBe('set')
expect(attr?.value).toEqual({ flight: 'UA123', seat: '12A' })
})

it('should set action to remove for null value', () => {
const payload: AttributesPayload = {
named_user_id: 'test-user',
occurred: occurred.toISOString(),
attributes: {
favourite_teams: null
}
}
const result = _private._build_attributes_object(payload)
const attr = result.find((a: any) => a.key === 'favourite_teams')
expect(attr?.action).toBe('remove')
})
})

describe('Testing _build_tags_object', () => {
it('should correctly format a tag', () => {
expect(_private._build_tags_object(valid_tags_payload)).toEqual(airship_tags_payload)
Expand Down Expand Up @@ -212,3 +269,93 @@ describe('Testing _parse_and_format_date', () => {
expect(_private._parse_and_format_date('foo')).toEqual('foo')
})
})

describe('Testing _channel_type_to_key', () => {
it('should map known platform types to platform-specific keys', () => {
expect(_private._channel_type_to_key('ios')).toBe('ios_channel')
expect(_private._channel_type_to_key('android')).toBe('android_channel')
expect(_private._channel_type_to_key('amazon')).toBe('amazon_channel')
expect(_private._channel_type_to_key('web')).toBe('web_channel')
})

it('should fall back to the generic channel key for unknown types', () => {
expect(_private._channel_type_to_key('email')).toBe('channel')
expect(_private._channel_type_to_key('sms')).toBe('channel')
})
})

describe('Testing _build_audience', () => {
it('should return named_user_id audience when only named_user_id is provided', () => {
expect(_private._build_audience({ named_user_id: 'user-123' })).toEqual({ named_user_id: 'user-123' })
})

it('should use the platform-specific key when channel_type maps to one', () => {
expect(_private._build_audience({ channel_id: 'chan-abc', channel_type: 'ios' })).toEqual({ ios_channel: 'chan-abc' })
})

it('should use the generic channel key when channel_type is not a known platform', () => {
expect(_private._build_audience({ channel_id: 'chan-abc', channel_type: 'email' })).toEqual({ channel: 'chan-abc' })
})

it('should use the generic channel key when channel_type is omitted', () => {
expect(_private._build_audience({ channel_id: 'chan-abc' })).toEqual({ channel: 'chan-abc' })
})

it('should prefer channel_id over named_user_id when both provided', () => {
expect(_private._build_audience({ named_user_id: 'user-123', channel_id: 'chan-abc', channel_type: 'ios' })).toEqual({
ios_channel: 'chan-abc'
})
})

it('should throw when neither named_user_id nor channel_id is provided', () => {
expect(() => _private._build_audience({})).toThrow('Either Named User ID or Channel ID must be provided')
})
})

describe('Testing _build_custom_event_object with channel_id', () => {
it('should use the platform-specific key for a known channel type', () => {
const payload: CustomEventsPayload = {
channel_id: 'bddb6f5d-dcc3-4b0b-ba50-61b169077302',
channel_type: 'ios',
name: 'Test Event',
occurred: occurred.toISOString(),
enable_batching: false
}
const result = _private._build_custom_event_object(payload) as any
expect(result.user).toEqual({ ios_channel: 'bddb6f5d-dcc3-4b0b-ba50-61b169077302' })
})

it('should use the generic channel key when channel_type is omitted', () => {
const payload: CustomEventsPayload = {
channel_id: 'chan-abc',
name: 'Test Event',
occurred: occurred.toISOString(),
enable_batching: false
}
const result = _private._build_custom_event_object(payload) as any
expect(result.user).toEqual({ channel: 'chan-abc' })
})
})

describe('Testing _build_tags_object with channel_id', () => {
it('should use the platform-specific key for a known channel type', () => {
const payload: ManageTagsPayload = {
channel_id: 'bddb6f5d-dcc3-4b0b-ba50-61b169077302',
channel_type: 'ios',
tag_group: 'segment-integration',
tags: { trait3: true }
}
const result = _private._build_tags_object(payload) as any
expect(result.audience).toEqual({ ios_channel: 'bddb6f5d-dcc3-4b0b-ba50-61b169077302' })
})

it('should use the generic channel key when channel_type is omitted', () => {
const payload: ManageTagsPayload = {
channel_id: 'chan-abc',
tag_group: 'segment-integration',
tags: { trait3: true }
}
const result = _private._build_tags_object(payload) as any
expect(result.audience).toEqual({ channel: 'chan-abc' })
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Array [
},
"occurred": false,
"user": Object {
"named_user_id": "5C5XZ3fWrH)z48",
"channel": "5C5XZ3fWrH)z48",
},
},
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Array [
},
"occurred": false,
"user": Object {
"named_user_id": "5C5XZ3fWrH)z48",
"channel": "5C5XZ3fWrH)z48",
},
},
]
Expand All @@ -31,7 +31,7 @@ Array [
},
"occurred": false,
"user": Object {
"named_user_id": "5C5XZ3fWrH)z48",
"channel": "5C5XZ3fWrH)z48",
},
},
]
Expand Down
Loading