diff --git a/packages/destination-actions/src/destinations/epsilon/__tests__/index.test.ts b/packages/destination-actions/src/destinations/epsilon/__tests__/index.test.ts new file mode 100644 index 0000000000..be1021c966 --- /dev/null +++ b/packages/destination-actions/src/destinations/epsilon/__tests__/index.test.ts @@ -0,0 +1,20 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import Definition from '../index' + +const testDestination = createTestIntegration(Definition) + +describe('Epsilon', () => { + describe('testAuthentication', () => { + it('should validate authentication inputs', async () => { + nock('https://your.destination.endpoint').get('*').reply(200, {}) + + const settings = { + username: '', + password: '' + } + + await expect(testDestination.testAuthentication(settings)).resolves.not.toThrowError() + }) + }) +}) diff --git a/packages/destination-actions/src/destinations/epsilon/__tests__/snapshot.test.ts b/packages/destination-actions/src/destinations/epsilon/__tests__/snapshot.test.ts new file mode 100644 index 0000000000..605d973009 --- /dev/null +++ b/packages/destination-actions/src/destinations/epsilon/__tests__/snapshot.test.ts @@ -0,0 +1,77 @@ +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import { generateTestData } from '../../../lib/test-data' +import destination from '../index' +import nock from 'nock' + +const testDestination = createTestIntegration(destination) +const destinationSlug = 'actions-epsilon' + +describe(`Testing snapshot for ${destinationSlug} destination:`, () => { + for (const actionSlug in destination.actions) { + it(`${actionSlug} action - required fields`, async () => { + const seedName = `${destinationSlug}#${actionSlug}` + const action = destination.actions[actionSlug] + const [eventData, settingsData] = generateTestData(seedName, destination, action, true) + + nock(/.*/).persist().get(/.*/).reply(200) + nock(/.*/).persist().post(/.*/).reply(200) + nock(/.*/).persist().put(/.*/).reply(200) + + const event = createTestEvent({ + properties: eventData + }) + + const responses = await testDestination.testAction(actionSlug, { + event: event, + mapping: event.properties, + settings: settingsData, + auth: undefined + }) + + const request = responses[0].request + const rawBody = await request.text() + + try { + const json = JSON.parse(rawBody) + expect(json).toMatchSnapshot() + return + } catch (err) { + expect(rawBody).toMatchSnapshot() + } + + expect(request.headers).toMatchSnapshot() + }) + + it(`${actionSlug} action - all fields`, async () => { + const seedName = `${destinationSlug}#${actionSlug}` + const action = destination.actions[actionSlug] + const [eventData, settingsData] = generateTestData(seedName, destination, action, false) + + nock(/.*/).persist().get(/.*/).reply(200) + nock(/.*/).persist().post(/.*/).reply(200) + nock(/.*/).persist().put(/.*/).reply(200) + + const event = createTestEvent({ + properties: eventData + }) + + const responses = await testDestination.testAction(actionSlug, { + event: event, + mapping: event.properties, + settings: settingsData, + auth: undefined + }) + + const request = responses[0].request + const rawBody = await request.text() + + try { + const json = JSON.parse(rawBody) + expect(json).toMatchSnapshot() + return + } catch (err) { + expect(rawBody).toMatchSnapshot() + } + }) + } +}) diff --git a/packages/destination-actions/src/destinations/epsilon/appOpen/__tests__/index.test.ts b/packages/destination-actions/src/destinations/epsilon/appOpen/__tests__/index.test.ts new file mode 100644 index 0000000000..a231de83e0 --- /dev/null +++ b/packages/destination-actions/src/destinations/epsilon/appOpen/__tests__/index.test.ts @@ -0,0 +1,9 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import Destination from '../../index' + +const testDestination = createTestIntegration(Destination) + +describe('Epsilon.send', () => { + // TODO: Test your action +}) diff --git a/packages/destination-actions/src/destinations/epsilon/appOpen/__tests__/snapshot.test.ts b/packages/destination-actions/src/destinations/epsilon/appOpen/__tests__/snapshot.test.ts new file mode 100644 index 0000000000..aae0ced2e7 --- /dev/null +++ b/packages/destination-actions/src/destinations/epsilon/appOpen/__tests__/snapshot.test.ts @@ -0,0 +1,75 @@ +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import { generateTestData } from '../../../../lib/test-data' +import destination from '../../index' +import nock from 'nock' + +const testDestination = createTestIntegration(destination) +const actionSlug = 'send' +const destinationSlug = 'Epsilon' +const seedName = `${destinationSlug}#${actionSlug}` + +describe(`Testing snapshot for ${destinationSlug}'s ${actionSlug} destination action:`, () => { + it('required fields', async () => { + const action = destination.actions[actionSlug] + const [eventData, settingsData] = generateTestData(seedName, destination, action, true) + + nock(/.*/).persist().get(/.*/).reply(200) + nock(/.*/).persist().post(/.*/).reply(200) + nock(/.*/).persist().put(/.*/).reply(200) + + const event = createTestEvent({ + properties: eventData + }) + + const responses = await testDestination.testAction(actionSlug, { + event: event, + mapping: event.properties, + settings: settingsData, + auth: undefined + }) + + const request = responses[0].request + const rawBody = await request.text() + + try { + const json = JSON.parse(rawBody) + expect(json).toMatchSnapshot() + return + } catch (err) { + expect(rawBody).toMatchSnapshot() + } + + expect(request.headers).toMatchSnapshot() + }) + + it('all fields', async () => { + const action = destination.actions[actionSlug] + const [eventData, settingsData] = generateTestData(seedName, destination, action, false) + + nock(/.*/).persist().get(/.*/).reply(200) + nock(/.*/).persist().post(/.*/).reply(200) + nock(/.*/).persist().put(/.*/).reply(200) + + const event = createTestEvent({ + properties: eventData + }) + + const responses = await testDestination.testAction(actionSlug, { + event: event, + mapping: event.properties, + settings: settingsData, + auth: undefined + }) + + const request = responses[0].request + const rawBody = await request.text() + + try { + const json = JSON.parse(rawBody) + expect(json).toMatchSnapshot() + return + } catch (err) { + expect(rawBody).toMatchSnapshot() + } + }) +}) diff --git a/packages/destination-actions/src/destinations/epsilon/appOpen/generated-types.ts b/packages/destination-actions/src/destinations/epsilon/appOpen/generated-types.ts new file mode 100644 index 0000000000..944d22b085 --- /dev/null +++ b/packages/destination-actions/src/destinations/epsilon/appOpen/generated-types.ts @@ -0,0 +1,3 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload {} diff --git a/packages/destination-actions/src/destinations/epsilon/appOpen/index.ts b/packages/destination-actions/src/destinations/epsilon/appOpen/index.ts new file mode 100644 index 0000000000..d027aba3c3 --- /dev/null +++ b/packages/destination-actions/src/destinations/epsilon/appOpen/index.ts @@ -0,0 +1,24 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' +import { cachebuster, formId, identifiers } from '../fields' + +const action: ActionDefinition = { + title: 'App Open', + description: 'Sync an App Open events to Epsilon', + defaultSubscription: 'type = "track" and event = "Application Opened"', + fields: { + cachebuster, + formId, + identifiers + }, + perform: (request, data) => { + // Make your partner api request here! + // return request('https://example.com', { + // method: 'post', + // json: data.payload + // }) + } +} + +export default action diff --git a/packages/destination-actions/src/destinations/epsilon/fields.ts b/packages/destination-actions/src/destinations/epsilon/fields.ts new file mode 100644 index 0000000000..1dff616a2d --- /dev/null +++ b/packages/destination-actions/src/destinations/epsilon/fields.ts @@ -0,0 +1,70 @@ +import { InputField } from '@segment/actions-core/destination-kit/types' + +export const eventName: InputField = { + label: 'Event Name', + description: 'The name of the event to send to Epsilon.', + type: 'string', + required: true, + default: { + '@path': '$.event' + } +} + +export const formId: InputField = { + label: 'Form ID', + description: 'Form ID used in Epsilon’s system to identify app visits vs. conversions.', + type: 'string', + required: true +} + +export const identifiers: InputField = { + label: 'Identifiers', + description: 'Unique identifiers for the user.', + type: 'object', + required: true, + properties: { + emailHash: { + label: 'Email address', + description: 'Accepts hashed or unhashed emails. Segment will ensure that a non hashed email is hashed before being sent to Epsilon', + type: 'string', + default: { + '@if': { + exists: { '@path': '$.context.traits.email' }, + then: { '@path': '$.context.traits.email' }, + else: { '@path': '$.properties.email' } + } + } + }, + userAgent: { + label: 'User Agent', + description: 'User agent of the mobile device.', + type: 'string', + default: { '@path': '$.context.userAgent' } + }, + userIp: { + label: 'IP Address', + description: 'IP address of the user.', + type: 'string', + default: { '@path': '$.context.ip' } + }, + userId: { + label: 'User ID', + description: 'Unique identifier for the user.', + type: 'string', + default: { '@path': '$.userId' } + }, + dvcid: { + label: 'Device ID', + description: 'Unique identifier for the device.', + type: 'string', + default: { '@path': '$.context.device.id' } + } + } +} + +export const cachebuster: InputField = { + label: 'Cache Buster', + description: 'Unique identifier for the message. Used for cache busting.', + type: 'string', + default: { '@path': '$.messageId' } +} \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/epsilon/generated-types.ts b/packages/destination-actions/src/destinations/epsilon/generated-types.ts new file mode 100644 index 0000000000..2074a6bfca --- /dev/null +++ b/packages/destination-actions/src/destinations/epsilon/generated-types.ts @@ -0,0 +1,12 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Settings { + /** + * Your Epsilon username + */ + username: string + /** + * Your Epsilon password. + */ + password: string +} diff --git a/packages/destination-actions/src/destinations/epsilon/index.ts b/packages/destination-actions/src/destinations/epsilon/index.ts new file mode 100644 index 0000000000..6a22277495 --- /dev/null +++ b/packages/destination-actions/src/destinations/epsilon/index.ts @@ -0,0 +1,59 @@ +import type { DestinationDefinition } from '@segment/actions-core' +import type { Settings } from './generated-types' + +import send from './appOpen' + +const destination: DestinationDefinition = { + name: 'Epsilon (Actions)', + slug: 'actions-epsilon', + mode: 'cloud', + description: 'Sync analytics events and user profile details to Epsilon', + + authentication: { + scheme: 'basic', + fields: { + username: { + label: 'Username', + description: 'Your Epsilon username', + type: 'string', + required: true + }, + password: { + label: 'password', + description: 'Your Epsilon password.', + type: 'string', + required: true + }, + companyId: { + label: 'Company ID', + description: 'Your Company ID. Contact Epsilon support for assistance.', + type: 'number', + required: true + }, + }, + testAuthentication: (request) => { + // Return a request that tests/validates the user's credentials. + // If you do not have a way to validate the authentication fields safely, + // you can remove the `testAuthentication` function, though discouraged. + } + }, + + extendRequest({ settings }) { + return { + username: settings.username, + password: settings.password + } + }, + + onDelete: async (request, { settings, payload }) => { + // Return a request that performs a GDPR delete for the provided Segment userId or anonymousId + // provided in the payload. If your destination does not support GDPR deletion you should not + // implement this function and should remove it completely. + }, + + actions: { + send + } +} + +export default destination