UNPKG

@transferwise/approve-api-action-helpers

Version:

An http client that handles SCA protected requests gracefully

143 lines (124 loc) 4.71 kB
/* eslint-disable fp/no-mutation */ import { Flow, Mode, runFlow } from './authenticationFlow'; import { http } from './http'; import { create } from './request'; jest.mock('./http'); jest.mock('./authenticationFlow'); // eslint-disable-next-line jest/prefer-lowercase-title describe('SCA request wrapper', () => { let request; beforeEach(() => { request = create(); }); it('lets you do a normal request and gives you the body with it', async () => { http.mockResolvedValue({ response: true }); expect(http).not.toHaveBeenCalled(); const params = { iAmRequestParams: true }; const response = await request('https://example.com', params); expect(http).toHaveBeenCalledWith('https://example.com', params); expect(response).toStrictEqual({ response: true }); }); it('lets you handle request errors without invoking the SCA flow', async () => { expect.assertions(2); http.mockRejectedValue({ response: { test: true } }); try { await request('https://example.com'); } catch (error) { // eslint-disable-next-line jest/no-conditional-expect expect(error.response.test).toBe(true); } expect(runFlow).not.toHaveBeenCalled(); }); describe('when SCA is required', () => { beforeEach(() => { request = create({ mode: Mode.SANDBOX, flow: Flow.IFRAME }); http.mockRejectedValue({ response: { status: 403, headers: fakeHeaders({ 'X-2FA-APPROVAL': 'a token' }) }, }); runFlow.mockImplementation(() => { http.mockResolvedValue({ secondCall: true }); return Promise.resolve(); }); }); it('runs the authentication flow with the right config values', async () => { expect(runFlow).not.toHaveBeenCalled(); await request('https://example.com'); expect(runFlow).toHaveBeenCalledWith({ token: 'a token', mode: Mode.SANDBOX, flow: Flow.IFRAME, approvalPageUrl: undefined, }); }); it('runs the authentication flow with the approvalPageUrl passed in', async () => { request = create({ approvalPageUrl: 'https://a.b/' }); expect(runFlow).not.toHaveBeenCalled(); await request('https://example.com'); expect(runFlow).toHaveBeenCalledWith( expect.objectContaining({ approvalPageUrl: 'https://a.b/' }), ); }); it('runs the original request with the 2fa token on success', async () => { request = create(); const result = await request('https://example.com'); expect(http).toHaveBeenNthCalledWith( 2, 'https://example.com', expect.objectContaining({ headers: { 'X-2FA-APPROVAL': 'a token', }, }), ); expect(result).toStrictEqual({ secondCall: true }); }); it('executes on SCA required callback', async () => { const onSCARequired = jest.fn(); request = create({ onSCARequired }); expect(onSCARequired).not.toHaveBeenCalled(); await request('https://example.com'); // eslint-disable-next-line jest/prefer-called-with expect(onSCARequired).toHaveBeenCalled(); }); it('executes on SCA complete callback', async () => { const onSCACompleted = jest.fn(); request = create({ onSCACompleted }); expect(onSCACompleted).not.toHaveBeenCalled(); await request('https://example.com'); // eslint-disable-next-line jest/prefer-called-with expect(onSCACompleted).toHaveBeenCalled(); }); }); describe('when metadata is requested', () => { it('returns that sca was not triggered for normal requests', async () => { http.mockResolvedValue({ response: true }); const params = { withMetadata: true }; const response = await request('https://example.com', params); expect(response).toStrictEqual({ metadata: { scaRequired: false }, response: { response: true }, }); }); it('returns that sca was triggered for requests where sca was used', async () => { // eslint-disable-next-line sonarjs/no-identical-functions http.mockRejectedValue({ response: { status: 403, headers: fakeHeaders({ 'X-2FA-APPROVAL': 'a token' }) }, }); runFlow.mockImplementation(() => { http.mockResolvedValue({ secondCall: true }); return Promise.resolve(); }); const params = { withMetadata: true }; const response = await request('https://example.com', params); expect(response).toStrictEqual({ metadata: { scaRequired: true }, response: { secondCall: true }, }); }); }); function fakeHeaders(object) { return { get: (key) => object[key], }; } });