@transferwise/approve-api-action-helpers
Version:
An http client that handles SCA protected requests gracefully
143 lines (124 loc) • 4.71 kB
JavaScript
/* 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],
};
}
});