UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

394 lines (319 loc) 9.51 kB
import util from 'util'; import Core from './Core'; import _ from 'lodash'; import { AxiosError } from 'axios'; describe('sdk/Core', () => { let client; beforeEach(() => { client = new Core(); client._axios = { request: jest.fn(), defaults: {}, }; }); afterEach(() => { jest.restoreAllMocks(); }); describe('constructor', () => { it('creates a new client with default configuration', () => { client = new Core(); expect(client._config).toEqual({ baseUrl: 'http://localhost:3001', timeout: 10000, token: 'token', debug: false, maxRetry: 3, retriableMethods: ['get', 'head', 'options'], retriableErrors: ['socket hang up'], }); }); it('creates a new client with configuration defined in the signature', () => { client = new Core({ baseUrl: 'https://datastore.org', token: 'private_token', }); expect(client._config).toMatchObject({ baseUrl: 'https://datastore.org', token: 'private_token', }); }); it('creates a new client with axios instanciated', () => { client = new Core(); expect(client._axios).toHaveProperty('request'); expect(client._axios).toHaveProperty('get'); expect(client._axios).toHaveProperty('post'); expect(client._axios).toHaveProperty('put'); expect(client._axios).toHaveProperty('delete'); expect(client._axios.defaults.headers).toMatchObject({ 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'token', }); }); it('creates a new client with telemetry enabled', () => { client = new Core({ telemetry: { logger: true }, }); expect(client._telemetry).toEqual({ logger: true }); }); }); describe('#paramsSerializer', () => { it('serializes simple query params correctly', () => { expect( Core.paramsSerializer({ a: 1, }), ).toEqual('q=' + encodeURIComponent(JSON.stringify({ a: 1 })) + '&a=1'); }); it('serializes nested object correctly', () => { expect( Core.paramsSerializer({ a: { b: 1, }, }), ).toEqual( 'q=' + encodeURIComponent( JSON.stringify({ a: { b: 1, }, }), ) + '&a%5Bb%5D=1', ); }); it('serializes arrays correctly', () => { expect( Core.paramsSerializer({ a: ['b', 'c', 'd'], }), ).toEqual( 'q=' + encodeURIComponent( JSON.stringify({ a: ['b', 'c', 'd'], }), ) + '&a%5B%5D=b&a%5B%5D=c&a%5B%5D=d', ); }); it('sends the empty array to the datastore', () => { expect( Core.paramsSerializer({ account_id: [], }), ).toEqual( 'q=' + encodeURIComponent(JSON.stringify({ account_id: [] })) + '&', ); }); }); describe('#inspect', () => { it('returns the object only if `debug=false`', () => { client._config.debug = false; console.log = jest.fn(); expect(client.inspect({ a: 1 })).toEqual({ a: 1 }); expect(console.log).toHaveBeenCalledTimes(0); }); it('returns the object and log details of object if `debug=true`', () => { client._config.debug = true; console.log = jest.fn(); expect(client.inspect({ a: 1 })).toEqual({ a: 1 }); expect(console.log).toHaveBeenCalledTimes(1); expect(console.log).toHaveBeenCalledWith( util.inspect({ a: 1 }, false, null, true), ); }); }); describe('#setTimeout', () => { it('forces the value of the axios timeout', () => { client.setTimeout(1200); expect(client._axios.defaults.timeout).toEqual(1200); }); }); describe('#getPath', () => { it('returns the path fragments', () => { expect(client.getPath('a', 'b')).toEqual('/api/a/b'); }); }); describe('#request', () => { it('performs a request on the client', async () => { await client.request({ method: 'get', url: '/heartbeat', }); expect(client._axios.request).toHaveBeenCalledTimes(1); expect(client._axios.request).toHaveBeenLastCalledWith({ method: 'get', url: '/heartbeat', headers: { 'force-primary': undefined, }, }); }); it('throws an error in case of exception', async () => { client.inspect = jest.fn(); client._axios = { request: jest.fn().mockImplementation(() => { const err = new Error('Ooops'); // @ts-ignore err.response = { data: { code: 500, message: 'Internal Server Error' }, }; throw err; }), }; let error; try { await client.request({ method: 'get', url: '/heartbeat', }); } catch (err) { error = err; } expect(error).toBeInstanceOf(Error); expect(error.message).toEqual('Ooops'); expect(client.inspect).toHaveBeenCalledTimes(1); expect(client.inspect).toHaveBeenCalledWith(error); }); it('throws an error in case of exception but do not inspect if not an HTTP Error', async () => { client.inspect = jest.fn(); client._axios = { request: jest.fn().mockImplementation(() => { const err = new Error('Ooops'); throw err; }), }; let error; try { await client.request({ method: 'get', url: '/heartbeat', }); } catch (err) { error = err; } expect(error).toBeInstanceOf(Error); expect(error.message).toEqual('Ooops'); expect(client.inspect).toHaveBeenCalledTimes(1); }); }); describe('#responseInterceptor', () => { it('throws the error in case of missing request configuration', async () => { const error = new AxiosError(); let _error; try { client.responseInterceptor(error); } catch (err) { _error = err; } expect(_error).toEqual(error); }); it('throws the error in case of non retriable method', async () => { const error = new AxiosError(); // @ts-ignore error.config = { method: 'post', }; let _error; try { client.responseInterceptor(error); } catch (err) { _error = err; } expect(_error).toEqual(error); }); it('throws the error in case if the max retry number exhausted', async () => { const error = new AxiosError('socket hang up'); // @ts-ignore error.config = { // @ts-ignore _retry: 3, }; let _error; try { client.responseInterceptor(error); } catch (err) { _error = err; } expect(_error).toEqual(error); }); it('throws the error in case of non matching error message', async () => { const error = new AxiosError('non matching'); // @ts-ignore error.config = { method: 'get', }; let _error; try { client.responseInterceptor(error); } catch (err) { _error = err; } expect(_error).toEqual(error); }); it('increments the `_retry` count in case of retriable request', async () => { const error = new AxiosError('socket hang up'); // @ts-ignore error.config = { method: 'get', }; client._axios = jest.fn(); client.responseInterceptor(error); expect(error.config).toHaveProperty('_retry', 1); }); it('increments the `_retry` count in case of retriable request with special `_q` stringified param', async () => { const error = new AxiosError('socket hang up'); // @ts-ignore error.config = { method: 'get', params: { _q: JSON.stringify({ hello: 'world' }), }, }; client._axios = jest.fn(); client.responseInterceptor(error); expect(error.config).toHaveProperty('_retry', 1); }); it('retries the request if retriable', async () => { const error = new AxiosError('socket hang up'); // @ts-ignore error.config = { method: 'get', }; client._axios = jest.fn(); client.responseInterceptor(error); expect(client._axios).toHaveBeenCalledWith(error.config); }); it('retries the request if retriable with a custom method', async () => { client = new Core({ retriableMethods: ['post'], }); const error = new AxiosError('socket hang up'); // @ts-ignore error.config = { method: 'post', }; client._axios = jest.fn(); client.responseInterceptor(error); expect(client._axios).toHaveBeenCalledWith(error.config); }); it('retries the request if retriable with a custom retry max value', async () => { client = new Core({ maxRetry: 5, }); const error = new AxiosError('socket hang up'); // @ts-ignore error.config = { method: 'get', // @ts-ignore _retry: 4, }; client._axios = jest.fn(); client.responseInterceptor(error); expect(client._axios).toHaveBeenCalledWith(error.config); }); }); });