From c80d11e9c5483d00232bb6534186226f0c520c97 Mon Sep 17 00:00:00 2001 From: bbantavo Date: Mon, 20 Jan 2025 13:55:00 +0100 Subject: [PATCH] [Antavo ] New Antavo Destination (#2619) * antavo action destination * fixed extension name + destination name * ran yarn types command * updated default mappings * updated field descriptions * added unit tests * wording update --- .../__snapshots__/snapshot.test.ts.snap | 49 ++++ .../antavo/__tests__/snapshot.test.ts | 77 ++++++ .../__snapshots__/snapshot.test.ts.snap | 21 ++ .../antavo/event/__tests__/event.test.ts | 193 +++++++++++++ .../antavo/event/__tests__/snapshot.test.ts | 75 ++++++ .../antavo/event/generated-types.ts | 22 ++ .../src/destinations/antavo/event/index.ts | 52 ++++ .../destinations/antavo/generated-types.ts | 12 + .../src/destinations/antavo/index.ts | 34 +++ .../__snapshots__/snapshot.test.ts.snap | 29 ++ .../antavo/profile/__tests__/profile.test.ts | 254 ++++++++++++++++++ .../antavo/profile/__tests__/snapshot.test.ts | 75 ++++++ .../antavo/profile/generated-types.ts | 50 ++++ .../src/destinations/antavo/profile/index.ts | 118 ++++++++ 14 files changed, 1061 insertions(+) create mode 100644 packages/destination-actions/src/destinations/antavo/__tests__/__snapshots__/snapshot.test.ts.snap create mode 100644 packages/destination-actions/src/destinations/antavo/__tests__/snapshot.test.ts create mode 100644 packages/destination-actions/src/destinations/antavo/event/__tests__/__snapshots__/snapshot.test.ts.snap create mode 100644 packages/destination-actions/src/destinations/antavo/event/__tests__/event.test.ts create mode 100644 packages/destination-actions/src/destinations/antavo/event/__tests__/snapshot.test.ts create mode 100644 packages/destination-actions/src/destinations/antavo/event/generated-types.ts create mode 100644 packages/destination-actions/src/destinations/antavo/event/index.ts create mode 100644 packages/destination-actions/src/destinations/antavo/generated-types.ts create mode 100644 packages/destination-actions/src/destinations/antavo/index.ts create mode 100644 packages/destination-actions/src/destinations/antavo/profile/__tests__/__snapshots__/snapshot.test.ts.snap create mode 100644 packages/destination-actions/src/destinations/antavo/profile/__tests__/profile.test.ts create mode 100644 packages/destination-actions/src/destinations/antavo/profile/__tests__/snapshot.test.ts create mode 100644 packages/destination-actions/src/destinations/antavo/profile/generated-types.ts create mode 100644 packages/destination-actions/src/destinations/antavo/profile/index.ts diff --git a/packages/destination-actions/src/destinations/antavo/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/antavo/__tests__/__snapshots__/snapshot.test.ts.snap new file mode 100644 index 0000000000..5c3a6d1c32 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/__tests__/__snapshots__/snapshot.test.ts.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Testing snapshot for actions-antavo destination: event action - all fields 1`] = ` +Object { + "account": "YOUfi0^J9NW]3LPm", + "action": "YOUfi0^J9NW]3LPm", + "api_key": "YOUfi0^J9NW]3LPm", + "customer": "YOUfi0^J9NW]3LPm", + "data": Object { + "testType": "YOUfi0^J9NW]3LPm", + }, +} +`; + +exports[`Testing snapshot for actions-antavo destination: event action - required fields 1`] = ` +Object { + "action": "YOUfi0^J9NW]3LPm", + "api_key": "YOUfi0^J9NW]3LPm", + "customer": "YOUfi0^J9NW]3LPm", +} +`; + +exports[`Testing snapshot for actions-antavo destination: profile action - all fields 1`] = ` +Object { + "account": "0$ZK&EN", + "action": "profile", + "api_key": "0$ZK&EN", + "customer": "0$ZK&EN", + "data": Object { + "birth_date": "0$ZK&EN", + "email": "oc@mij.mo", + "first_name": "0$ZK&EN", + "gender": "0$ZK&EN", + "language": "0$ZK&EN", + "last_name": "0$ZK&EN", + "mobile_phone": "0$ZK&EN", + "phone": "0$ZK&EN", + }, +} +`; + +exports[`Testing snapshot for actions-antavo destination: profile action - required fields 1`] = ` +Object { + "action": "profile", + "api_key": "0$ZK&EN", + "customer": "0$ZK&EN", + "data": Object {}, +} +`; diff --git a/packages/destination-actions/src/destinations/antavo/__tests__/snapshot.test.ts b/packages/destination-actions/src/destinations/antavo/__tests__/snapshot.test.ts new file mode 100644 index 0000000000..627f86c5d2 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/__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-antavo' + +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/antavo/event/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/antavo/event/__tests__/__snapshots__/snapshot.test.ts.snap new file mode 100644 index 0000000000..4bf08a0874 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/event/__tests__/__snapshots__/snapshot.test.ts.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Testing snapshot for Antavo's event destination action: all fields 1`] = ` +Object { + "account": "IB60PM0Tc", + "action": "IB60PM0Tc", + "api_key": "IB60PM0Tc", + "customer": "IB60PM0Tc", + "data": Object { + "testType": "IB60PM0Tc", + }, +} +`; + +exports[`Testing snapshot for Antavo's event destination action: required fields 1`] = ` +Object { + "action": "IB60PM0Tc", + "api_key": "IB60PM0Tc", + "customer": "IB60PM0Tc", +} +`; diff --git a/packages/destination-actions/src/destinations/antavo/event/__tests__/event.test.ts b/packages/destination-actions/src/destinations/antavo/event/__tests__/event.test.ts new file mode 100644 index 0000000000..8df4c9549c --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/event/__tests__/event.test.ts @@ -0,0 +1,193 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import destination from '../../index' + +const testDestination = createTestIntegration(destination) +const settings = { + stack: 'test-stack', + api_key: 'testApiKey' +} + +describe('Antavo (Actions)', () => { + beforeEach((done) => { + nock.cleanAll() + nock.abortPendingRequests() + done() + }) + + describe('event', () => { + it('Handle request with default mappings', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'track', + userId: 'testUser', + properties: { + antavoAction: 'testAction', + antavoAccount: 'testAccount', + points: 1234 + } + }) + + const responses = await testDestination.testAction( + 'event', { + event, + settings, + mapping: { + customer: { '@path': '$.userId' }, + action: { '@path': '$.properties.antavoAction' }, + account: { '@path': '$.properties.antavoAccount' }, + data: { + points: { '@path': '$.properties.points' } + } + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(202) + expect(responses[0].options.json).toMatchObject({ + customer: 'testUser', + action: 'testAction', + account: 'testAccount', + data: { + points: 1234 + }, + api_key: 'testApiKey' + }) + }) + it('Handle request without default mappings', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'track', + properties: { + antavoUserId: 'testUser', + antavoAction: 'testAction', + antavoAccount: 'testAccount', + points: 1234 + } + }) + + const responses = await testDestination.testAction( + 'event', { + event, + settings, + mapping: { + customer: { '@path': '$.properties.antavoUserId' }, + action: { '@path': '$.properties.antavoAction' }, + account: { '@path': '$.properties.antavoAccount' }, + data: { + points: { '@path': '$.properties.points' } + } + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(202) + expect(responses[0].options.json).toMatchObject({ + customer: 'testUser', + action: 'testAction', + account: 'testAccount', + data: { + points: 1234 + }, + api_key: 'testApiKey' + }) + }) + it('Handle request without optional fields', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'track', + userId: 'testUser', + properties: { + antavoAction: 'testAction' + } + }) + + const responses = await testDestination.testAction( + 'event', { + event, + settings, + mapping: { + customer: { '@path': '$.userId' }, + action: { '@path': '$.properties.antavoAction' } + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(202) + expect(responses[0].options.json).toMatchObject({ + customer: 'testUser', + action: 'testAction', + api_key: 'testApiKey' + }) + }) + it('Throw error for missing required field: customer', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'track', + userId: 'testUser', + properties: { + antavoAction: 'testAction', + antavoAccount: 'testAccount', + points: 1234 + } + }) + + await expect(testDestination.testAction( + 'event', { + event, + settings, + mapping: { + customer: '', + action: { '@path': '$.properties.antavoAction' }, + account: { '@path': '$.properties.antavoAccount' }, + data: { + points: { '@path': '$.properties.points' } + } + } + }) + ).rejects.toThrowError('The root value is missing the required field \'customer\'.') + }) + it('Throw error for missing required field: action', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'track', + userId: 'testUser', + properties: { + antavoAction: 'testAction', + antavoAccount: 'testAccount', + points: 1234 + } + }) + + await expect(testDestination.testAction( + 'event', { + event, + settings, + mapping: { + customer: { '@path': '$.userId' }, + action: '', + account: { '@path': '$.properties.antavoAccount' }, + data: { + points: { '@path': '$.properties.points' } + } + } + }) + ).rejects.toThrowError('The root value is missing the required field \'action\'.') + }) + }) +}) diff --git a/packages/destination-actions/src/destinations/antavo/event/__tests__/snapshot.test.ts b/packages/destination-actions/src/destinations/antavo/event/__tests__/snapshot.test.ts new file mode 100644 index 0000000000..c0e0307ad0 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/event/__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 = 'event' +const destinationSlug = 'Antavo' +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/antavo/event/generated-types.ts b/packages/destination-actions/src/destinations/antavo/event/generated-types.ts new file mode 100644 index 0000000000..1ef3733245 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/event/generated-types.ts @@ -0,0 +1,22 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * User ID, selected in Antavo as customer identifier + */ + customer: string + /** + * Loyalty event name in Antavo + */ + action: string + /** + * Antavo Account ID — if the Multi Accounts extension is enabled + */ + account?: string + /** + * Event data + */ + data?: { + [k: string]: unknown + } +} diff --git a/packages/destination-actions/src/destinations/antavo/event/index.ts b/packages/destination-actions/src/destinations/antavo/event/index.ts new file mode 100644 index 0000000000..7aa197093a --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/event/index.ts @@ -0,0 +1,52 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' + +const action: ActionDefinition = { + title: 'Loyalty events', + description: 'Sync loyalty events into Antavo', + defaultSubscription: 'type = "track"', + fields: { + customer: { + label: 'Customer ID', + description: 'User ID, selected in Antavo as customer identifier', + type: 'string', + required: true, + default: { + '@path': '$.userId' + } + }, + action: { + label: 'Action', + description: 'Loyalty event name in Antavo', + type: 'string', + required: true + }, + account: { + label: 'Account', + description: 'Antavo Account ID — if the Multi Accounts extension is enabled', + type: 'string', + required: false + }, + data: { + label: 'Event data', + description: 'Event data', + type: 'object', + required: false + } + }, + perform: (request, data) => { + const url = `https://api.${data.settings.stack}.antavo.com/v1/webhook/segment` + const payload = { + ...data.payload, + api_key: data.settings.api_key + } + + return request(url, { + method: 'post', + json: payload + }) + } +} + +export default action diff --git a/packages/destination-actions/src/destinations/antavo/generated-types.ts b/packages/destination-actions/src/destinations/antavo/generated-types.ts new file mode 100644 index 0000000000..400d7a3a92 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/generated-types.ts @@ -0,0 +1,12 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Settings { + /** + * The Antavo Loyalty Engine stack where your brand resides + */ + stack: string + /** + * The Antavo brand API key supplied to your brand in Antavo Loyalty Engine + */ + api_key: string +} diff --git a/packages/destination-actions/src/destinations/antavo/index.ts b/packages/destination-actions/src/destinations/antavo/index.ts new file mode 100644 index 0000000000..3d79d1ad83 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/index.ts @@ -0,0 +1,34 @@ +import type { DestinationDefinition } from '@segment/actions-core' +import type { Settings } from './generated-types' + +import event from './event' +import profile from './profile' + +const destination: DestinationDefinition = { + name: 'Antavo (Actions)', + slug: 'actions-antavo', + mode: 'cloud', + authentication: { + scheme: 'custom', + fields: { + stack: { + label: 'Stack', + description: 'The Antavo Loyalty Engine stack where your brand resides', + type: 'string', + required: true + }, + api_key: { + label: 'API Key', + description: 'The Antavo brand API key supplied to your brand in Antavo Loyalty Engine', + type: 'password', + required: true + } + } + }, + actions: { + event, + profile + } +} + +export default destination diff --git a/packages/destination-actions/src/destinations/antavo/profile/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/antavo/profile/__tests__/__snapshots__/snapshot.test.ts.snap new file mode 100644 index 0000000000..1bc641c447 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/profile/__tests__/__snapshots__/snapshot.test.ts.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Testing snapshot for Antavo's profile destination action: all fields 1`] = ` +Object { + "account": "x7T1S", + "action": "profile", + "api_key": "x7T1S", + "customer": "x7T1S", + "data": Object { + "birth_date": "x7T1S", + "email": "kukaspot@te.al", + "first_name": "x7T1S", + "gender": "x7T1S", + "language": "x7T1S", + "last_name": "x7T1S", + "mobile_phone": "x7T1S", + "phone": "x7T1S", + }, +} +`; + +exports[`Testing snapshot for Antavo's profile destination action: required fields 1`] = ` +Object { + "action": "profile", + "api_key": "x7T1S", + "customer": "x7T1S", + "data": Object {}, +} +`; diff --git a/packages/destination-actions/src/destinations/antavo/profile/__tests__/profile.test.ts b/packages/destination-actions/src/destinations/antavo/profile/__tests__/profile.test.ts new file mode 100644 index 0000000000..edab8408d3 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/profile/__tests__/profile.test.ts @@ -0,0 +1,254 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import destination from '../../index' + +const testDestination = createTestIntegration(destination) +const settings = { + stack: 'test-stack', + api_key: 'testApiKey' +} + +describe('Antavo (Actions)', () => { + beforeEach((done) => { + nock.cleanAll() + nock.abortPendingRequests() + done() + }) + + describe('profile', () => { + it('Handle request with default mappings', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'identify', + userId: 'testUser', + traits: { + antavoAccount: 'testAccount', + first_name: 'testFirstName', + last_name: 'testLastName', + email: 'test@test.com', + birth_date: '1900-01-01', + gender: 'testGender', + language: 'testLanguage', + phone: 123456, + mobile_phone: 654321 + } + }) + + const responses = await testDestination.testAction( + 'profile', { + event, + settings, + mapping: { + customer: { '@path': '$.userId' }, + account: { '@path': '$.traits.antavoAccount' }, + data: { + first_name: { '@path': '$.traits.first_name' }, + last_name: { '@path': '$.traits.last_name' }, + email: { '@path': '$.traits.email' }, + birth_date: { '@path': '$.traits.birth_date' }, + gender: { '@path': '$.traits.gender' }, + language: { '@path': '$.traits.language' }, + phone: { '@path': '$.traits.phone' }, + mobile_phone: { '@path': '$.traits.mobile_phone' } + } + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(202) + expect(responses[0].options.json).toMatchObject({ + customer: 'testUser', + account: 'testAccount', + data: { + first_name: 'testFirstName', + last_name: 'testLastName', + email: 'test@test.com', + birth_date: '1900-01-01', + gender: 'testGender', + language: 'testLanguage', + phone: '123456', + mobile_phone: '654321' + }, + action: 'profile', + api_key: 'testApiKey' + }) + }) + it('Handle request without default mappings', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'identify', + userId: 'testUser', + traits: { + antavoAccount: 'testAccount', + antavoFirstName: 'testFirstName', + antavoLastName: 'testLastName', + antavoEmail: 'test@test.com', + antavoBirthDate: '1900-01-01', + antavoGender: 'testGender', + antavoLanguage: 'testLanguage', + antavoPhone: 123456, + antavoMobilePhone: 654321 + } + }) + + const responses = await testDestination.testAction( + 'profile', { + event, + settings, + mapping: { + customer: { '@path': '$.userId' }, + account: { '@path': '$.traits.antavoAccount' }, + data: { + first_name: { '@path': '$.traits.antavoFirstName' }, + last_name: { '@path': '$.traits.antavoLastName' }, + email: { '@path': '$.traits.antavoEmail' }, + birth_date: { '@path': '$.traits.antavoBirthDate' }, + gender: { '@path': '$.traits.antavoGender' }, + language: { '@path': '$.traits.antavoLanguage' }, + phone: { '@path': '$.traits.antavoPhone' }, + mobile_phone: { '@path': '$.traits.antavoMobilePhone' } + } + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(202) + expect(responses[0].options.json).toMatchObject({ + customer: 'testUser', + account: 'testAccount', + data: { + first_name: 'testFirstName', + last_name: 'testLastName', + email: 'test@test.com', + birth_date: '1900-01-01', + gender: 'testGender', + language: 'testLanguage', + phone: '123456', + mobile_phone: '654321' + }, + action: 'profile', + api_key: 'testApiKey' + }) + }) + it('Handle request without optional fields', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'identify', + userId: 'testUser', + traits: { + first_name: 'testFirstName', + last_name: 'testLastName', + email: 'test@test.com', + birth_date: '1900-01-01', + gender: 'testGender', + language: 'testLanguage', + phone: 123456, + mobile_phone: 654321 + } + }) + + const responses = await testDestination.testAction( + 'profile', { + event, + settings, + mapping: { + customer: { '@path': '$.userId' }, + data: { + first_name: { '@path': '$.traits.first_name' }, + last_name: { '@path': '$.traits.last_name' }, + email: { '@path': '$.traits.email' }, + birth_date: { '@path': '$.traits.birth_date' }, + gender: { '@path': '$.traits.gender' }, + language: { '@path': '$.traits.language' }, + phone: { '@path': '$.traits.phone' }, + mobile_phone: { '@path': '$.traits.mobile_phone' } + } + } + }) + + expect(responses.length).toBe(1) + expect(responses[0].status).toBe(202) + expect(responses[0].options.json).toMatchObject({ + customer: 'testUser', + data: { + first_name: 'testFirstName', + last_name: 'testLastName', + email: 'test@test.com', + birth_date: '1900-01-01', + gender: 'testGender', + language: 'testLanguage', + phone: '123456', + mobile_phone: '654321' + }, + action: 'profile', + api_key: 'testApiKey' + }) + }) + it('Throw error for missing required field: customer', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'identify', + userId: 'testUser', + traits: { + first_name: 'testFirstName', + last_name: 'testLastName', + email: 'test@test.com', + telephone: 123456 + } + }) + + await expect(testDestination.testAction( + 'profile', { + event, + settings, + mapping: { + data: { + first_name: { '@path': '$.traits.first_name' }, + last_name: { '@path': '$.traits.last_name' }, + email: { '@path': '$.traits.email' }, + mobile_phone: { '@path': '$.traits.telephone' } + } + } + }) + ).rejects.toThrowError('The root value is missing the required field \'customer\'.') + }) + it('Throw error for missing required field: data', async () => { + nock(`https://api.${settings.stack}.antavo.com`) + .post('/v1/webhook/segment') + .reply(202, {}) + + const event = createTestEvent({ + type: 'identify', + userId: 'testUser', + traits: { + first_name: 'testFirstName', + last_name: 'testLastName', + email: 'test@test.com', + telephone: 123456 + } + }) + + await expect(testDestination.testAction( + 'profile', { + event, + settings, + mapping: { + customer: { '@path': '$.userId' } + } + }) + ).rejects.toThrowError('The root value is missing the required field \'data\'.') + }) + }) +}) diff --git a/packages/destination-actions/src/destinations/antavo/profile/__tests__/snapshot.test.ts b/packages/destination-actions/src/destinations/antavo/profile/__tests__/snapshot.test.ts new file mode 100644 index 0000000000..75883c2461 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/profile/__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 = 'profile' +const destinationSlug = 'Antavo' +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() + console.log(rawBody) + 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/antavo/profile/generated-types.ts b/packages/destination-actions/src/destinations/antavo/profile/generated-types.ts new file mode 100644 index 0000000000..d502e77a25 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/profile/generated-types.ts @@ -0,0 +1,50 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * User ID, selected in Antavo as customer identifier + */ + customer: string + /** + * Antavo Account ID — if the Multi Accounts extension is enabled + */ + account?: string + /** + * Customer properties + */ + data: { + /** + * Customer's first name + */ + first_name?: string + /** + * Customer's last name + */ + last_name?: string + /** + * Customer's email address + */ + email?: string + /** + * Customer's birth date + */ + birth_date?: string + /** + * Customer's gender + */ + gender?: string + /** + * Customer's language + */ + language?: string + /** + * Customer's phone number + */ + phone?: string + /** + * Customer's mobile phone number + */ + mobile_phone?: string + [k: string]: unknown + } +} diff --git a/packages/destination-actions/src/destinations/antavo/profile/index.ts b/packages/destination-actions/src/destinations/antavo/profile/index.ts new file mode 100644 index 0000000000..52cd5aab15 --- /dev/null +++ b/packages/destination-actions/src/destinations/antavo/profile/index.ts @@ -0,0 +1,118 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' + +const action: ActionDefinition = { + title: 'Profile updates', + description: 'Sync profile updates into Antavo', + defaultSubscription: 'type = "identify"', + fields: { + customer: { + label: 'Customer ID', + description: 'User ID, selected in Antavo as customer identifier', + type: 'string', + required: true, + default: { + '@path': '$.userId' + } + }, + account: { + label: 'Account', + description: 'Antavo Account ID — if the Multi Accounts extension is enabled', + type: 'string', + required: false, + default: '' + }, + data: { + label: 'Data', + description: 'Customer properties', + type: 'object', + defaultObjectUI: 'keyvalue', + required: true, + additionalProperties: true, + properties: { + first_name: { + label: 'First name', + description: 'Customer\'s first name', + type: 'string' + }, + last_name: { + label: 'Last name', + description: 'Customer\'s last name', + type: 'string' + }, + email: { + label: 'Email', + description: 'Customer\'s email address', + type: 'string' + }, + birth_date: { + label: 'Birthdate', + description: 'Customer\'s birth date', + type: 'string' + }, + gender: { + label: 'Gender', + description: 'Customer\'s gender', + type: 'string' + }, + language: { + label: 'Language', + description: 'Customer\'s language', + type: 'string' + }, + phone: { + label: 'Phone', + description: 'Customer\'s phone number', + type: 'string' + }, + mobile_phone: { + label: 'Mobile phone', + description: 'Customer\'s mobile phone number', + type: 'string' + }, + }, + default: { + first_name: { + '@path': '$.traits.first_name' + }, + last_name: { + '@path': '$.traits.last_name' + }, + email: { + '@path': '$.traits.email' + }, + birth_date: { + '@path': '$.traits.birthday' + }, + gender: { + '@path': '$.traits.gender' + }, + language: { + '@path': '$.traits.language' + }, + phone: { + '@path': '$.traits.phone' + }, + mobile_phone: { + '@path': '$.traits.mobile_phone' + } + } + }, + }, + perform: (request, data) => { + const url = `https://api.${data.settings.stack}.antavo.com/v1/webhook/segment` + const payload = { + ...data.payload, + action: 'profile', + api_key: data.settings.api_key + } + + return request(url, { + method: 'post', + json: payload + }) + } +} + +export default action