import fs from 'fs'; import FormData from 'form-data'; import MockAdapter from 'axios-mock-adapter'; import MessengerClient from '../MessengerClient'; const USER_ID = '1QAZ2WSX'; const ACCESS_TOKEN = '1234567890'; const APP_ID = '987654321'; const APP_SECRET = '1WDVGY78'; const APP_ACCESS_TOKEN = 'APP_ACCESS_TOKEN'; let axios; let _create; beforeEach(() => { axios = require('axios'); _create = axios.create; }); afterEach(() => { axios.create = _create; }); const createMock = (): { client: MessengerClient; mock: MockAdapter } => { const client = new MessengerClient({ appId: APP_ID, appSecret: APP_SECRET, accessToken: ACCESS_TOKEN, skipAppSecretProof: true, }); const mock = new MockAdapter(client.axios); return { client, mock }; }; describe('page info', () => { describe('#getPageInfo', () => { it('should response page info', async () => { const { client, mock } = createMock(); const reply = { name: 'Bot Demo', id: '1895382890692546', }; let url; let params; mock.onGet().reply((config) => { url = config.url; params = config.params; return [200, reply]; }); const res = await client.getPageInfo(); expect(url).toEqual('/me'); expect(params).toEqual({ access_token: ACCESS_TOKEN, fields: undefined, }); expect(res).toEqual(reply); }); }); it('should support fields', async () => { const { client, mock } = createMock(); const reply = { name: 'Bot Demo', id: '1895382890692546', }; let url; let params; mock.onGet().reply((config) => { url = config.url; params = config.params; return [200, reply]; }); const res = await client.getPageInfo({ fields: ['id', 'name'], }); expect(url).toEqual('/me'); expect(params).toEqual({ access_token: ACCESS_TOKEN, fields: 'id,name', }); expect(res).toEqual(reply); }); }); describe('token', () => { describe('#debugToken', () => { it('should response token info', async () => { expect.assertions(3); const { client, mock } = createMock(); const reply = { data: { app_id: '000000000000000', application: 'Social Cafe', expires_at: 1352419328, is_valid: true, issued_at: 1347235328, scopes: ['email', 'user_location'], user_id: 1207059, }, }; let url; let params; mock.onGet().reply((config) => { url = config.url; params = config.params; return [200, reply]; }); const res = await client.debugToken(); expect(url).toEqual('/debug_token'); expect(params).toEqual({ input_token: ACCESS_TOKEN, access_token: `${APP_ID}|${APP_SECRET}`, }); expect(res).toEqual({ appId: '000000000000000', application: 'Social Cafe', expiresAt: 1352419328, isValid: true, issuedAt: 1347235328, scopes: ['email', 'user_location'], userId: 1207059, }); }); }); }); describe('subscription', () => { describe('#createSubscription', () => { it('should set default fields', async () => { const { client, mock } = createMock(); const reply = { success: true, }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.createSubscription({ appId: '54321', callbackUrl: 'https://mycallback.com', verifyToken: '1234567890', accessToken: APP_ACCESS_TOKEN, }); expect(url).toEqual( `/${APP_ID}/subscriptions?access_token=${APP_ACCESS_TOKEN}` ); expect(JSON.parse(data)).toEqual({ object: 'page', callback_url: 'https://mycallback.com', fields: 'messages,messaging_postbacks,messaging_optins,messaging_referrals,messaging_handovers,messaging_policy_enforcement', verify_token: '1234567890', }); expect(res).toEqual(reply); }); it('should set other optional parameters', async () => { const { client, mock } = createMock(); const reply = { success: true, }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.createSubscription({ appId: '54321', callbackUrl: 'https://mycallback.com', verifyToken: '1234567890', object: 'user', fields: ['messages', 'messaging_postbacks'], includeValues: true, accessToken: APP_ACCESS_TOKEN, }); expect(url).toEqual( `/${APP_ID}/subscriptions?access_token=${APP_ACCESS_TOKEN}` ); expect(JSON.parse(data)).toEqual({ object: 'user', callback_url: 'https://mycallback.com', fields: 'messages,messaging_postbacks', verify_token: '1234567890', include_values: true, }); expect(res).toEqual(reply); }); }); describe('#getSubscriptions', () => { it('should get current subscriptions', async () => { const { client, mock } = createMock(); const reply = { data: [ { object: 'page', callback_url: 'https://mycallback.com', active: true, fields: [ { name: 'messages', version: 'v2.12', }, ], }, ], }; let url; mock.onGet().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.getSubscriptions(); expect(url).toEqual( `/${APP_ID}/subscriptions?access_token=${APP_ID}|${APP_SECRET}` ); expect(res).toEqual([ { object: 'page', callbackUrl: 'https://mycallback.com', active: true, fields: [ { name: 'messages', version: 'v2.12', }, ], }, ]); }); }); describe('#getPageSubscription', () => { it('should get current page subscription', async () => { const { client, mock } = createMock(); const reply = { data: [ { object: 'page', callback_url: 'https://mycallback.com', active: true, fields: [ { name: 'messages', version: 'v2.12', }, ], }, { object: 'user', callback_url: 'https://mycallback.com', active: true, fields: [ { name: 'feed', version: 'v2.12', }, ], }, ], }; let url; mock.onGet().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.getPageSubscription(); expect(url).toEqual( `/${APP_ID}/subscriptions?access_token=${APP_ID}|${APP_SECRET}` ); expect(res).toEqual({ object: 'page', callbackUrl: 'https://mycallback.com', active: true, fields: [ { name: 'messages', version: 'v2.12', }, ], }); }); }); }); describe('#getMessagingFeatureReview', () => { it('should response feature array', async () => { const { client, mock } = createMock(); const reply = { data: [ { feature: 'subscription_messaging', status: 'approved', }, ], }; let url; mock.onGet().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.getMessagingFeatureReview(); expect(url).toEqual( `/me/messaging_feature_review?access_token=${ACCESS_TOKEN}` ); expect(res).toEqual([ { feature: 'subscription_messaging', status: 'approved', }, ]); }); }); describe('user profile', () => { describe('#getUserProfile', () => { it('should get user profile with default fields', async () => { const { client, mock } = createMock(); const reply = { id: '1', first_name: 'Kevin', last_name: 'Durant', profile_pic: 'https://example.com/pic.png', }; let url; mock.onGet().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.getUserProfile('1'); expect(url).toEqual( `/1?fields=id,name,first_name,last_name,profile_pic&access_token=${ACCESS_TOKEN}` ); expect(res).toEqual({ id: '1', firstName: 'Kevin', lastName: 'Durant', profilePic: 'https://example.com/pic.png', }); }); it('should get user profile with given fields', async () => { const { client, mock } = createMock(); const reply = { id: '1', first_name: 'Kevin', last_name: 'Durant', profile_pic: 'https://example.com/pic.png', locale: 'en_US', timezone: 8, gender: 'male', }; let url; mock.onGet().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.getUserProfile('1', { fields: [ 'id', 'name', 'first_name', 'last_name', 'profile_pic', 'locale', 'timezone', 'gender', ], }); expect(url).toEqual( `/1?fields=id,name,first_name,last_name,profile_pic,locale,timezone,gender&access_token=${ACCESS_TOKEN}` ); expect(res).toEqual({ id: '1', firstName: 'Kevin', lastName: 'Durant', profilePic: 'https://example.com/pic.png', locale: 'en_US', timezone: 8, gender: 'male', }); }); }); }); describe('upload api', () => { describe('#uploadAttachment', () => { it('should call messages api to upload attachment', async () => { const { client, mock } = createMock(); const reply = { attachment_id: '1854626884821032', }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.uploadAttachment( 'image', 'http://www.yoctol-rocks.com/image.jpg' ); expect(url).toEqual( `/me/message_attachments?access_token=${ACCESS_TOKEN}` ); expect(JSON.parse(data)).toEqual({ message: { attachment: { type: 'image', payload: { url: 'http://www.yoctol-rocks.com/image.jpg', is_reusable: false, }, }, }, }); expect(res).toEqual({ attachmentId: '1854626884821032', }); }); it('can upload reusable attachment', async () => { const { client, mock } = createMock(); const reply = { attachment_id: '1854626884821032', }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.uploadAttachment( 'image', 'http://www.yoctol-rocks.com/image.jpg', { isReusable: true } ); expect(url).toEqual( `/me/message_attachments?access_token=${ACCESS_TOKEN}` ); expect(JSON.parse(data)).toEqual({ message: { attachment: { type: 'image', payload: { url: 'http://www.yoctol-rocks.com/image.jpg', is_reusable: true, }, }, }, }); expect(res).toEqual({ attachmentId: '1854626884821032', }); }); it('can call api with file stream', async () => { const { client, mock } = createMock(); const reply = { attachment_id: '1854626884821032', }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.uploadAttachment( 'file', fs.createReadStream('./') ); expect(url).toEqual( `/me/message_attachments?access_token=${ACCESS_TOKEN}` ); expect(data).toBeInstanceOf(FormData); expect(res).toEqual({ attachmentId: '1854626884821032', }); }); }); describe('#uploadAudio', () => { it('should call messages api to upload audio', async () => { const { client, mock } = createMock(); const reply = { attachment_id: '1854626884821032', }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.uploadAudio( 'http://www.yoctol-rocks.com/audio.mp3' ); expect(url).toEqual( `/me/message_attachments?access_token=${ACCESS_TOKEN}` ); expect(JSON.parse(data)).toEqual({ message: { attachment: { type: 'audio', payload: { url: 'http://www.yoctol-rocks.com/audio.mp3', is_reusable: false, }, }, }, }); expect(res).toEqual({ attachmentId: '1854626884821032', }); }); }); describe('#uploadImage', () => { it('should call messages api to upload image', async () => { const { client, mock } = createMock(); const reply = { attachment_id: '1854626884821032', }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.uploadImage( 'http://www.yoctol-rocks.com/image.jpg' ); expect(url).toEqual( `/me/message_attachments?access_token=${ACCESS_TOKEN}` ); expect(JSON.parse(data)).toEqual({ message: { attachment: { type: 'image', payload: { url: 'http://www.yoctol-rocks.com/image.jpg', is_reusable: false, }, }, }, }); expect(res).toEqual({ attachmentId: '1854626884821032', }); }); }); describe('#uploadVideo', () => { it('should call messages api to upload video', async () => { const { client, mock } = createMock(); const reply = { attachment_id: '1854626884821032', }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.uploadVideo( 'http://www.yoctol-rocks.com/video.mp4' ); expect(url).toEqual( `/me/message_attachments?access_token=${ACCESS_TOKEN}` ); expect(JSON.parse(data)).toEqual({ message: { attachment: { type: 'video', payload: { url: 'http://www.yoctol-rocks.com/video.mp4', is_reusable: false, }, }, }, }); expect(res).toEqual({ attachmentId: '1854626884821032', }); }); }); describe('#uploadFile', () => { it('should call messages api to upload file', async () => { const { client, mock } = createMock(); const reply = { attachment_id: '1854626884821032', }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.uploadFile( 'http://www.yoctol-rocks.com/file.pdf' ); expect(url).toEqual( `/me/message_attachments?access_token=${ACCESS_TOKEN}` ); expect(JSON.parse(data)).toEqual({ message: { attachment: { type: 'file', payload: { url: 'http://www.yoctol-rocks.com/file.pdf', is_reusable: false, }, }, }, }); expect(res).toEqual({ attachmentId: '1854626884821032', }); }); }); }); describe('Built-in NLP API', () => { describe('#setNLPConfigs', () => { it('should call api to set NLP configs', async () => { const { client, mock } = createMock(); const reply = { success: true, }; let url; mock.onPost().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.setNLPConfigs({ nlpEnabled: true, customToken: '1234567890', }); expect(url).toEqual( `/me/nlp_configs?nlp_enabled=true&custom_token=1234567890&access_token=1234567890` ); expect(res).toEqual(reply); }); }); describe('#enableNLP', () => { it('should call api to enable NLP', async () => { const { client, mock } = createMock(); const reply = { success: true, }; let url; mock.onPost().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.enableNLP(); expect(url).toEqual( `/me/nlp_configs?nlp_enabled=true&access_token=1234567890` ); expect(res).toEqual(reply); }); }); describe('#disableNLP', () => { it('should call api to disable NLP', async () => { const { client, mock } = createMock(); const reply = { success: true, }; let url; mock.onPost().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.disableNLP(); expect(url).toEqual( `/me/nlp_configs?nlp_enabled=false&access_token=1234567890` ); expect(res).toEqual(reply); }); }); }); describe('Event Logging API', () => { describe('#logCustomEvents', () => { it('should call api to log events', async () => { const { client, mock } = createMock(); const reply = { success: true, }; let url; let data; mock.onPost().reply((config) => { url = config.url; data = config.data; return [200, reply]; }); const res = await client.logCustomEvents({ appId: 12345, pageId: 67890, pageScopedUserId: USER_ID, events: [ { _eventName: 'fb_mobile_purchase', _valueToSum: 55.22, _fb_currency: 'USD', }, ], }); expect(url).toEqual(`/12345/activities`); expect(JSON.parse(data)).toEqual({ event: 'CUSTOM_APP_EVENTS', custom_events: '[{"_eventName":"fb_mobile_purchase","_valueToSum":55.22,"_fb_currency":"USD"}]', advertiser_tracking_enabled: 0, application_tracking_enabled: 0, extinfo: '["mb1"]', page_id: 67890, page_scoped_user_id: USER_ID, }); expect(res).toEqual(reply); }); }); }); describe('ID Matching', () => { describe('getIdsForApps', () => { it('should call api with appsecret_proof', async () => { const { client, mock } = createMock(); const reply = { data: [ { id: '10152368852405295', app: { category: 'Business', link: 'https://www.facebook.com/games/?app_id=1419232575008550', name: "John's Game App", id: '1419232575008550', }, }, { id: '645195294', app: { link: 'https://apps.facebook.com/johnsmovieappns/', name: 'JohnsMovieApp', namespace: 'johnsmovieappns', id: '259773517400382', }, }, ], paging: { cursors: { before: 'MTQ4OTU4MjQ5Nzc4NjY4OAZDZDA', after: 'NDAwMDExOTA3MDM1ODMwA', }, }, }; let url; mock.onGet().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.getIdsForApps({ userId: '12345123', appSecret: APP_SECRET, page: '5678', }); expect(url).toEqual( `/12345123/ids_for_apps?access_token=${ACCESS_TOKEN}&appsecret_proof=4894f81b47c53ccf240a1130d119db2c69833eac9be09adeebc8e7226fb73e73&page=5678` ); expect(res).toEqual(reply); }); }); describe('getIdsForPages', () => { it('should call api with appsecret_proof', async () => { const { client, mock } = createMock(); const reply = { data: [ { id: '12345123', // The psid for the user for that page page: { category: 'Musician', link: 'https://www.facebook.com/Johns-Next-Great-Thing-380374449010653/', name: "John's Next Great Thing", id: '380374449010653', }, }, ], paging: { cursors: { before: 'MTQ4OTU4MjQ5Nzc4NjY4OAZDZDA', after: 'NDAwMDExOTA3MDM1ODMwA', }, }, }; let url; mock.onGet().reply((config) => { url = config.url; return [200, reply]; }); const res = await client.getIdsForPages({ userId: '12345123', appSecret: APP_SECRET, app: '5678', }); expect(url).toEqual( `/12345123/ids_for_pages?access_token=${ACCESS_TOKEN}&appsecret_proof=4894f81b47c53ccf240a1130d119db2c69833eac9be09adeebc8e7226fb73e73&app=5678` ); expect(res).toEqual(reply); }); }); }); describe('Error', () => { it('should be formatted correctly', async () => { const { client, mock } = createMock(); const reply = { error: { message: 'Invalid OAuth access token.', type: 'OAuthException', code: 190, error_subcode: 1234567, fbtrace_id: 'BLBz/WZt8dN', }, }; mock.onAny().reply(400, reply); let error; try { await client.sendText(USER_ID, 'Hello!'); } catch (err) { error = err; } expect(error.message).toEqual( 'Messenger API - 190 OAuthException Invalid OAuth access token.' ); }); });