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
@@ -0,0 +1,95 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Testing snapshot for actions-kochava destination: event action - all fields 1`] = `
Object {
"action": "event",
"data": Object {
"app_tracking_transparency": Object {
"att": false,
"att_detail": "c)30knD9jX&cI",
"att_duration": 1955441609277.44,
"att_time": 1955441609277.44,
},
"app_version": "c)30knD9jX&cI",
"currency": "AMD",
"device_ids": Object {
"adid": "c)30knD9jX&cI",
"android_id": "c)30knD9jX&cI",
"idfa": "c)30knD9jX&cI",
"idfv": "c)30knD9jX&cI",
},
"device_limit_tracking": true,
"device_ua": "c)30knD9jX%26cI",
"device_ver": "c)30knD9jX&cI",
"event_data": Object {
"testType": "c)30knD9jX&cI",
},
"event_name": "c)30knD9jX&cI",
"origination_ip": "c)30knD9jX&cI",
"usertime": 1612137600,
},
"kochava_app_id": "c)30knD9jX&cI",
"kochava_device_id": "c)30knD9jX&cI",
}
`;

exports[`Testing snapshot for actions-kochava destination: event action - required fields 1`] = `
Object {
"action": "event",
"data": Object {
"device_ids": Object {
"adid": "c)30knD9jX&cI",
"android_id": "c)30knD9jX&cI",
"idfa": "c)30knD9jX&cI",
"idfv": "c)30knD9jX&cI",
},
"event_name": "c)30knD9jX&cI",
},
"kochava_app_id": "c)30knD9jX&cI",
"kochava_device_id": "c)30knD9jX&cI",
}
`;

exports[`Testing snapshot for actions-kochava destination: install action - all fields 1`] = `
Object {
"action": "install",
"data": Object {
"ad_services_token": "2m*qGXENfE",
"app_tracking_transparency": Object {
"att": true,
"att_detail": "2m*qGXENfE",
"att_duration": -32716306922864.64,
"att_time": -32716306922864.64,
},
"app_version": "2m*qGXENfE",
"device_ids": Object {
"adid": "2m*qGXENfE",
"android_id": "2m*qGXENfE",
"idfa": "2m*qGXENfE",
"idfv": "2m*qGXENfE",
},
"device_ua": "2m*qGXENfE",
"device_ver": "2m*qGXENfE",
"origination_ip": "2m*qGXENfE",
"usertime": 1612137600,
},
"kochava_app_id": "2m*qGXENfE",
"kochava_device_id": "2m*qGXENfE",
}
`;

exports[`Testing snapshot for actions-kochava destination: install action - required fields 1`] = `
Object {
"action": "install",
"data": Object {
"device_ids": Object {
"adid": "2m*qGXENfE",
"android_id": "2m*qGXENfE",
"idfa": "2m*qGXENfE",
"idfv": "2m*qGXENfE",
},
},
"kochava_app_id": "2m*qGXENfE",
"kochava_device_id": "2m*qGXENfE",
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/**
* E2E tests for Kochava (Actions)
*
* These tests make real HTTP calls to a running local serve server
* (./bin/run serve), which in turn makes real outbound calls to Kochava's
* https://control.kochava.com/track/json endpoint. They are NOT run in CI.
*
* Prerequisites:
* 1. Start the serve server (in a separate terminal):
* ./bin/run serve kochava --noUI
*
* 2. Set environment variables (or create a .env file):
* export BASE_URL=http://localhost:3000
* export KOCHAVA_APP_ID=<a Kochava App GUID; a dummy value is fine for validation paths>
*
* 3. Run the tests:
* yarn cloud jest --testPathPattern="kochava/__tests__/e2e"
*/

import http from 'http'

// Jest's node test environment does not expose undici's global `fetch`, and
// serve listens on IPv4 only, so use the built-in http module against 127.0.0.1.
const BASE_URL = process.env.BASE_URL ?? 'http://127.0.0.1:3000'
const KOCHAVA_APP_ID = process.env.KOCHAVA_APP_ID ?? 'e2e-dummy-app-guid'

jest.setTimeout(30000)

function request(
method: 'GET' | 'POST',
path: string,
body?: Record<string, unknown>
): Promise<{ status: number; body: any }> {
return new Promise((resolve, reject) => {
const url = new URL(`${BASE_URL}${path}`)
const payload = body === undefined ? undefined : JSON.stringify(body)
const req = http.request(
{
hostname: url.hostname,
port: url.port,
path: url.pathname,
method,
headers:
payload === undefined
? undefined
: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
},
(res) => {
let data = ''
res.on('data', (chunk) => (data += chunk))
res.on('end', () => {
let parsed: unknown = null
try {
parsed = JSON.parse(data)
} catch {
parsed = data
}
resolve({ status: res.statusCode ?? 0, body: parsed })
})
}
)
req.on('error', reject)
if (payload !== undefined) req.write(payload)
req.end()
})
}

async function post(path: string, body: Record<string, unknown>) {
return request('POST', path, body)
}

async function get(path: string) {
return request('GET', path)
}

const settings = { kochava_app_id: KOCHAVA_APP_ID }

describe('Kochava (Actions) E2E', () => {
beforeAll(async () => {
try {
await get('/manifest')
} catch {
throw new Error('Serve server is not running. Start it with:\n ./bin/run serve kochava --noUI')
}
})

describe('Authentication', () => {
// No testAuthentication is defined; the framework default validates the
// settings schema. A valid required App GUID should authenticate.
it('returns ok:true for valid settings', async () => {
const res = await post('/authenticate', { kochava_app_id: KOCHAVA_APP_ID })
expect(res.status).toBe(200)
expect(res.body.ok).toBe(true)
})
})

describe('event (Post-Install Event)', () => {
it('delivers a post-install event via perform()', async () => {
const res = await post('/event', {
settings,
payload: {
type: 'track',
event: 'Subscription Started',
messageId: 'msg-e2e-event-1',
timestamp: '2021-03-20T18:06:56.000Z',
properties: { currency: 'USD', sum: 150 },
context: {
device: { id: 'device-e2e-1', advertisingId: 'idfa-e2e-1', adTrackingEnabled: true },
os: { version: '14.4' },
app: { version: '1.0.0' },
userAgent: 'Mozilla/5.0 (iPhone)',
ip: '104.219.46.66'
}
},
mapping: {
event_name: { '@path': '$.event' },
idfa: { '@path': '$.context.device.advertisingId' },
device_ua: { '@path': '$.context.userAgent' },
device_ver: { '@path': '$.context.os.version' },
origination_ip: { '@path': '$.context.ip' },
usertime: { '@path': '$.timestamp' }
}
})
expect(res.status).toBe(200)
expect(Array.isArray(res.body)).toBe(true)
})

it('honors kochava_app_id override in the mapping', async () => {
const res = await post('/event', {
settings,
payload: { type: 'track', event: 'Purchase', messageId: 'msg-e2e-event-2' },
mapping: {
kochava_app_id: 'override-guid-e2e',
event_name: 'Purchase',
idfa: 'idfa-e2e-2',
device_ver: '15.0'
}
})
expect(res.status).toBe(200)
expect(Array.isArray(res.body)).toBe(true)
})

it('returns a validation error when no device identifier is provided', async () => {
const res = await post('/event', {
settings,
payload: { type: 'track', event: 'Purchase', messageId: 'msg-e2e-event-3' },
mapping: {
event_name: 'Purchase',
device_ver: '15.0'
// no idfa / idfv / adid / android_id
}
})
expect(res.status).toBe(200)
expect(res.body[0].message).toContain('device identifier')
})

it('returns a validation error when event_name (required) is missing', async () => {
const res = await post('/event', {
settings,
payload: { type: 'track', messageId: 'msg-e2e-event-4' },
mapping: {
idfa: 'idfa-e2e-4',
device_ver: '15.0'
}
})
expect(res.status).toBe(200)
expect(res.body[0].message).toMatch(/event_name|required/i)
})
})

describe('install (Install Notification)', () => {
it('delivers an install notification via perform()', async () => {
const res = await post('/install', {
settings,
payload: {
type: 'track',
event: 'Application Installed',
messageId: 'msg-e2e-install-1',
timestamp: '2021-03-20T18:06:56.000Z',
context: {
device: { id: 'device-e2e-1', advertisingId: 'idfa-e2e-1' },
os: { version: '15.3' },
app: { version: '3.3.0' },
userAgent: 'Mozilla/5.0 (iPhone)',
ip: '77.224.141.10'
}
},
mapping: {
idfa: { '@path': '$.context.device.advertisingId' },
device_ver: { '@path': '$.context.os.version' },
app_version: { '@path': '$.context.app.version' },
origination_ip: { '@path': '$.context.ip' }
}
})
expect(res.status).toBe(200)
expect(Array.isArray(res.body)).toBe(true)
})

it('includes the ATT block and AdServices token when provided', async () => {
const res = await post('/install', {
settings,
payload: { type: 'track', event: 'Application Installed', messageId: 'msg-e2e-install-2' },
mapping: {
idfa: 'idfa-e2e-2',
device_ver: '15.0',
att: true,
att_time: 1616263616,
ad_services_token: 'as-token-e2e'
}
})
expect(res.status).toBe(200)
expect(Array.isArray(res.body)).toBe(true)
})

it('returns a validation error when no device identifier is provided', async () => {
const res = await post('/install', {
settings,
payload: { type: 'track', event: 'Application Installed', messageId: 'msg-e2e-install-3' },
mapping: {
device_ver: '15.0'
// no idfa / idfv / adid / android_id
}
})
expect(res.status).toBe(200)
expect(res.body[0].message).toContain('device identifier')
})
})

describe('Skipped (not testable via HTTP)', () => {
// Kochava exposes no auth-verification endpoint and this destination defines
// no testAuthentication, so there is no failure branch to trigger for invalid
// credentials beyond settings-schema validation.
it.skip('testAuthentication failure branch — no custom testAuthentication exists', () => {})

// No performBatch is implemented (Kochava /track/json accepts one record per
// call), so there is no batch delivery path to exercise.
it.skip('performBatch delivery path — not implemented (no batch endpoint)', () => {})

// 5xx/429 -> RetryableError and 4xx -> APIError mapping depend on Kochava's
// live response to a given payload, which cannot be forced deterministically
// over HTTP without a real (mis)configured App GUID.
it.skip('retryable vs non-retryable HTTP error mapping — depends on live Kochava response', () => {})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Destination from '../index'

describe('Kochava (Actions)', () => {
it('exposes the expected metadata', () => {
expect(Destination.name).toBe('Kochava (Actions)')
expect(Destination.slug).toBe('actions-kochava')
expect(Destination.mode).toBe('cloud')
})

it('registers the event and install actions', () => {
expect(Object.keys(Destination.actions)).toEqual(expect.arrayContaining(['event', 'install']))
})

it('requires the kochava_app_id setting', () => {
const fields = Destination.authentication?.fields
expect(fields?.kochava_app_id?.required).toBe(true)
})

it('defines presets for both actions', () => {
const partnerActions = (Destination.presets ?? []).map((p) => p.partnerAction)
expect(partnerActions).toEqual(expect.arrayContaining(['event', 'install']))
})
})
Loading
Loading