UNPKG

facebook-batch

Version:

Gracefully batching facebook requests.

406 lines (317 loc) 9.74 kB
import { MessengerClient } from 'messaging-api-messenger'; import { mocked } from 'ts-jest/utils'; import BatchRequestError from '../BatchRequestError'; import FacebookBatchQueue from '../FacebookBatchQueue'; import { isError613 } from '..'; jest.mock('messaging-api-messenger'); const { MessengerBatch } = jest.requireActual('messaging-api-messenger'); const image = { attachment: { type: 'image', payload: { url: 'https://cdn.free.com.tw/blog/wp-content/uploads/2014/08/Placekitten480-g.jpg', }, }, }; let queue: FacebookBatchQueue; function setup(options = {}): { client: MessengerClient; timeout: NodeJS.Timeout; } { // https://github.com/nodejs/node/blob/e1ad548cd4bfb996ea925584542f30c85aa3dfa1/lib/internal/timers.js#L202-L231 const timeout: NodeJS.Timeout = { hasRef: () => true, refresh: () => timeout, ref: () => timeout, unref: () => timeout, }; mocked(setTimeout).mockReturnValue(timeout); queue = new FacebookBatchQueue( { accessToken: 'ACCESS_TOKEN', appSecret: 'APP_SECRET', }, options ); const client = mocked(MessengerClient).mock.instances[0]; return { client, timeout, }; } afterEach(() => { queue.stop(); }); it('should push psid and messages to queue', () => { setup(); queue.push(MessengerBatch.sendMessage('1412611362105802', image)); expect(queue.queue).toHaveLength(1); }); it('should flush when length >= 50', async () => { const { client } = setup(); const responses = Array(50) .fill(0) .map(() => ({ code: 200, body: { data: [] }, })); mocked(client.sendBatch).mockResolvedValue(responses); for (let i = 0; i < 49; i++) { queue.push(MessengerBatch.sendText('1412611362105802', 'hello')); } queue.push(MessengerBatch.sendMessage('1412611362105802', image)); expect(client.sendBatch).toHaveBeenCalledTimes(1); expect(mocked(client.sendBatch).mock.calls[0][0]).toHaveLength(50); expect(mocked(client.sendBatch).mock.calls[0][0][49]).toEqual({ body: { message: { attachment: { payload: { url: 'https://cdn.free.com.tw/blog/wp-content/uploads/2014/08/Placekitten480-g.jpg', }, type: 'image', }, }, messagingType: 'UPDATE', recipient: { id: '1412611362105802' }, }, method: 'POST', relativeUrl: 'me/messages', }); expect(queue.queue).toHaveLength(0); }); it('should flush with 1000 timeout', async () => { const { client } = setup(); const responses = [ { code: 200, body: { data: [] }, }, ]; mocked(client.sendBatch).mockResolvedValue(responses); expect(setTimeout).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); queue.push(MessengerBatch.sendMessage('1412611362105802', image)); expect(queue.queue).toHaveLength(1); const fn = mocked(setTimeout).mock.calls[0][0]; await fn(); expect(client.sendBatch).toHaveBeenCalledTimes(1); expect(mocked(client.sendBatch).mock.calls[0][0]).toEqual([ { body: { message: { attachment: { payload: { url: 'https://cdn.free.com.tw/blog/wp-content/uploads/2014/08/Placekitten480-g.jpg', }, type: 'image', }, }, messagingType: 'UPDATE', recipient: { id: '1412611362105802' }, }, method: 'POST', relativeUrl: 'me/messages', }, ]); }); it('should not send batch when with empty array', async () => { const { client } = setup(); const responses = [ { code: 200, body: { data: [] }, }, ]; mocked(client.sendBatch).mockResolvedValue(responses); expect(setTimeout).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); const fn = mocked(setTimeout).mock.calls[0][0]; await fn(); expect(client.sendBatch).not.toBeCalled(); }); it('should reset timeout when flush', async () => { const { client, timeout } = setup(); const responses = Array(50) .fill(0) .map(() => ({ code: 200, body: { data: [] }, })); mocked(client.sendBatch).mockResolvedValue(responses); expect(setTimeout).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); for (let i = 0; i < 49; i++) { queue.push(MessengerBatch.sendText('1412611362105802', 'hello')); } queue.push(MessengerBatch.sendMessage('1412611362105802', image)); expect(clearTimeout).toHaveBeenCalledTimes(1); expect(clearTimeout).toHaveBeenLastCalledWith(timeout); expect(setTimeout).toHaveBeenCalledTimes(2); expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); }); it('should throw request and response', async () => { const { client } = setup(); const responses = [ { code: 400, body: { error: { message: '(#100) Param recipient[id] must be a valid ID string (e.g., "123")', }, }, }, ]; mocked(client.sendBatch).mockResolvedValue(responses); let error: BatchRequestError | undefined; queue .push(MessengerBatch.sendMessage('1412611362105802', image)) .catch((err) => { error = err; }); expect(setTimeout).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); const fn = mocked(setTimeout).mock.calls[0][0]; await fn(); expect(error).toBeDefined(); expect(error!.request).toEqual({ body: { message: { attachment: { payload: { url: 'https://cdn.free.com.tw/blog/wp-content/uploads/2014/08/Placekitten480-g.jpg', }, type: 'image', }, }, messagingType: 'UPDATE', recipient: { id: '1412611362105802' }, }, method: 'POST', relativeUrl: 'me/messages', }); expect(error!.response).toEqual({ body: { error: { message: '(#100) Param recipient[id] must be a valid ID string (e.g., "123")', }, }, code: 400, }); }); it('should support delay option', async () => { const { client } = setup({ delay: 500 }); const responses = [ { code: 200, body: { data: [] }, }, ]; mocked(client.sendBatch).mockResolvedValue(responses); expect(setTimeout).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 500); }); it('should support retryTimes option', async () => { const { client } = setup({ retryTimes: 3 }); const responses = [ { code: 400, body: { error: { message: '(#100) Param recipient[id] must be a valid ID string (e.g., "123")', }, }, }, ]; mocked(client.sendBatch).mockResolvedValue(responses); let error: BatchRequestError | undefined; queue .push(MessengerBatch.sendMessage('1412611362105802', image)) .catch((err) => { error = err; }); const fn = mocked(setTimeout).mock.calls[0][0]; await fn(); expect(error).not.toBeDefined(); await fn(); expect(error).not.toBeDefined(); await fn(); expect(error).not.toBeDefined(); await fn(); expect(error).toBeDefined(); }); it('should support shouldRetry option', async () => { const { client } = setup({ retryTimes: 1, shouldRetry: isError613, }); const responses = [ { code: 400, body: { error: { message: '(#100) Param recipient[id] must be a valid ID string (e.g., "123")', }, }, }, { code: 400, body: { error: { message: '(#613) Calls to this api have exceeded the rate limit.', }, }, }, ]; mocked(client.sendBatch).mockResolvedValue(responses); let error1: BatchRequestError | undefined; const request1 = MessengerBatch.sendMessage('1412611362105802', image); queue.push(request1).catch((err: BatchRequestError) => { error1 = err; }); let error2: BatchRequestError | undefined; const request2 = MessengerBatch.sendMessage('1412611362105802', image); queue.push(request2).catch((err: BatchRequestError) => { error2 = err; }); expect(queue.queue).toHaveLength(2); const fn = mocked(setTimeout).mock.calls[0][0]; await fn(); expect(queue.queue).toHaveLength(1); expect(client.sendBatch).toHaveBeenCalledTimes(1); expect(mocked(client.sendBatch).mock.calls[0][0]).toHaveLength(2); expect(mocked(client.sendBatch).mock.calls[0][0][0]).toBe(request1); expect(mocked(client.sendBatch).mock.calls[0][0][1]).toBe(request2); expect(error1).toBeDefined(); expect(error2).not.toBeDefined(); await fn(); expect(queue.queue).toHaveLength(0); expect(client.sendBatch).toHaveBeenCalledTimes(2); expect(mocked(client.sendBatch).mock.calls[1][0]).toHaveLength(1); expect(mocked(client.sendBatch).mock.calls[1][0][0]).toBe(request2); expect(error2).toBeDefined(); }); it('should reject every promise when call batch failed', async () => { const { client } = setup(); mocked(client.sendBatch).mockImplementation(() => { throw new Error('boom'); }); let error1: BatchRequestError | undefined; const request1 = MessengerBatch.sendMessage('1412611362105802', image); queue.push(request1).catch((err: BatchRequestError) => { error1 = err; }); let error2: BatchRequestError | undefined; const request2 = MessengerBatch.sendMessage('1412611362105802', image); queue.push(request2).catch((err: BatchRequestError) => { error2 = err; }); const fn = mocked(setTimeout).mock.calls[0][0]; await fn(); expect(error1).toBeDefined(); expect(error2).toBeDefined(); });