diff --git a/src/endpoint.ts b/src/endpoint.ts index cc697b5b..92a352e0 100644 --- a/src/endpoint.ts +++ b/src/endpoint.ts @@ -1,4 +1,4 @@ -import { deleteData, fetchData, insertParams, stringifyQuery } from './utils' +import { deleteData, fetchData, getData, insertParams, stringifyQuery } from './utils' import type { DeleteEndpoint, GetEndpoint, paths, PostEndpoint, Primitive, PutEndpoint } from './types/api' function makeUrl( @@ -37,10 +37,10 @@ export function getEndpoint( rawUrl?: string, ): Promise { if (rawUrl) { - return fetchData(rawUrl) + return getData(rawUrl) } const url = makeUrl(baseUrl, path as string, params?.path, params?.query) - return fetchData(url, undefined, undefined, params?.headers) + return getData(url, params?.headers) } export function deleteEndpoint( @@ -49,5 +49,5 @@ export function deleteEndpoint( params?: paths[T] extends DeleteEndpoint ? paths[T]['delete']['parameters'] : never, ): Promise { const url = makeUrl(baseUrl, path as string, params?.path) - return deleteData(url) + return deleteData(url, params?.headers) } diff --git a/src/utils.ts b/src/utils.ts index 37820861..b571c630 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -58,17 +58,12 @@ async function parseResponse(resp: Response): Promise { export async function fetchData( url: string, - method?: 'POST' | 'PUT', + method: 'POST' | 'PUT', body?: unknown, headers?: Record, ): Promise { - let options: - | { - method: 'POST' | 'PUT' - headers: Record - body: string - } - | undefined + let options: RequestInit | undefined + if (body != null) { const requestHeaders: Record = headers ?? {} requestHeaders['Content-Type'] = 'application/json' @@ -84,11 +79,35 @@ export async function fetchData( return parseResponse(resp) } -export async function deleteData(url: string): Promise { - const options = { +export async function getData(url: string, headers?: Record): Promise { + const options: RequestInit = { + method: 'GET', + } + + if (headers) { + options['headers'] = { + ...headers, + 'Content-Type': 'application/json', + } + } + + const resp = await fetch(url, options) + + return parseResponse(resp) +} + +export async function deleteData(url: string, headers?: Record): Promise { + const options: RequestInit = { method: 'DELETE', } + if (headers) { + options['headers'] = { + ...headers, + 'Content-Type': 'application/json', + } + } + const resp = await fetch(url, options) return parseResponse(resp) diff --git a/tests/endpoint.test.ts b/tests/endpoint.test.ts index e0640e06..b8cbd655 100644 --- a/tests/endpoint.test.ts +++ b/tests/endpoint.test.ts @@ -1,4 +1,4 @@ -import { fetchData } from '../src/utils' +import { getData, fetchData } from '../src/utils' import { getEndpoint, postEndpoint, putEndpoint } from '../src/endpoint' jest.mock('../src/utils', () => { @@ -8,6 +8,7 @@ jest.mock('../src/utils', () => { __esModule: true, ...originalModule, fetchData: jest.fn(() => Promise.resolve({ success: true })), + getData: jest.fn(() => Promise.resolve({ success: true })), } }) @@ -17,12 +18,7 @@ describe('getEndpoint', () => { success: true, }) - expect(fetchData).toHaveBeenCalledWith( - 'https://test.test/v1/balances/supported-fiat-codes', - undefined, - undefined, - undefined, - ) + expect(getData).toHaveBeenCalledWith('https://test.test/v1/balances/supported-fiat-codes', undefined) }) it('should accept a path param', async () => { @@ -32,7 +28,7 @@ describe('getEndpoint', () => { }), ).resolves.toEqual({ success: true }) - expect(fetchData).toHaveBeenCalledWith('https://test.test/v1/chains/4/safes/0x123', undefined, undefined, undefined) + expect(getData).toHaveBeenCalledWith('https://test.test/v1/chains/4/safes/0x123', undefined) }) it('should accept several path params', async () => { @@ -43,12 +39,7 @@ describe('getEndpoint', () => { }), ).resolves.toEqual({ success: true }) - expect(fetchData).toHaveBeenCalledWith( - 'https://test.test/v1/chains/4/safes/0x123/balances/usd', - undefined, - undefined, - undefined, - ) + expect(getData).toHaveBeenCalledWith('https://test.test/v1/chains/4/safes/0x123/balances/usd', undefined) }) it('should accept query params', async () => { @@ -59,11 +50,9 @@ describe('getEndpoint', () => { }), ).resolves.toEqual({ success: true }) - expect(fetchData).toHaveBeenCalledWith( + expect(getData).toHaveBeenCalledWith( 'https://test.test/v1/chains/4/safes/0x123/balances/usd?exclude_spam=true', undefined, - undefined, - undefined, ) }) @@ -110,7 +99,7 @@ describe('getEndpoint', () => { ), ).resolves.toEqual({ success: true }) - expect(fetchData).toHaveBeenCalledWith('/test-url?raw=true') + expect(getData).toHaveBeenCalledWith('/test-url?raw=true') }) it('should call a data decoder POST endpoint', async () => { diff --git a/tests/utils.test.ts b/tests/utils.test.ts index c03bd03f..b937cfff 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,4 +1,5 @@ -import { fetchData, insertParams, stringifyQuery } from '../src/utils' +/// +import { fetchData, getData, deleteData, insertParams, stringifyQuery } from '../src/utils' const fetchMock = jest.spyOn(global, 'fetch') as typeof fetch & jest.Mock @@ -28,7 +29,7 @@ describe('utils', () => { }) }) - describe('fetchData', () => { + describe('getData', () => { it('should fetch a simple url', async () => { fetchMock.mockImplementation(() => { return Promise.resolve({ @@ -38,10 +39,60 @@ describe('utils', () => { }) }) - await expect(fetchData('/test/safe?q=123')).resolves.toEqual({ success: true }) - expect(fetch).toHaveBeenCalledWith('/test/safe?q=123', undefined) + await expect(getData('/test/safe?q=123')).resolves.toEqual({ success: true }) + expect(fetch).toHaveBeenCalledWith('/test/safe?q=123', { method: 'GET' }) + }) + + it('should forward headers with a GET request', async () => { + fetchMock.mockImplementation(() => { + return Promise.resolve({ + ok: true, + text: () => Promise.resolve('{"success": "true"}'), + json: () => Promise.resolve({ success: true }), + }) + }) + + await expect(getData('/test/safe', { TestHeader: '123456' })).resolves.toEqual({ + success: true, + }) + + expect(fetch).toHaveBeenCalledWith('/test/safe', { + method: 'GET', + headers: { + TestHeader: '123456', + 'Content-Type': 'application/json', + }, + }) + }) + + it('should throw if response is not OK', async () => { + fetchMock.mockImplementation(() => { + return Promise.resolve({ + ok: false, + statusText: 'Failed', + json: () => ({ code: 1337, message: 'something went wrong' }), + }) + }) + + await expect(getData('/test/safe?q=123')).rejects.toThrow('1337: something went wrong') + expect(fetch).toHaveBeenCalledWith('/test/safe?q=123', { method: 'GET' }) + }) + + it('should throw the response text for 50x errors', async () => { + fetchMock.mockImplementation(() => { + return Promise.resolve({ + ok: false, + statusText: 'Failed', + json: () => null, + }) + }) + + await expect(getData('/test/safe?q=123')).rejects.toThrow('Failed') + expect(fetch).toHaveBeenCalledWith('/test/safe?q=123', { method: 'GET' }) }) + }) + describe('fetchData', () => { it('should make a post request', async () => { fetchMock.mockImplementation(() => { return Promise.resolve({ @@ -62,7 +113,7 @@ describe('utils', () => { }) }) - it('should forward headers', async () => { + it('should forward headers with a POST request', async () => { fetchMock.mockImplementation(() => { return Promise.resolve({ ok: true, @@ -103,31 +154,43 @@ describe('utils', () => { }, }) }) + }) - it('should throw if response is not OK', async () => { + describe('deleteData', () => { + it('should make a DELETE request', async () => { fetchMock.mockImplementation(() => { return Promise.resolve({ - ok: false, - statusText: 'Failed', - json: () => ({ code: 1337, message: 'something went wrong' }), + ok: true, + text: () => Promise.resolve('{"success": "true"}'), + json: () => Promise.resolve({ success: true }), }) }) - await expect(fetchData('/test/safe?q=123')).rejects.toThrow('1337: something went wrong') - expect(fetch).toHaveBeenCalledWith('/test/safe?q=123', undefined) + await expect(deleteData('/test/safe')).resolves.toEqual({ success: true }) + + expect(fetch).toHaveBeenCalledWith('/test/safe', { + method: 'DELETE', + }) }) - it('should throw the response text for 50x errors', async () => { + it('should make a DELETE request and pass headers', async () => { fetchMock.mockImplementation(() => { return Promise.resolve({ - ok: false, - statusText: 'Failed', - json: () => null, + ok: true, + text: () => Promise.resolve('{"success": "true"}'), + json: () => Promise.resolve({ success: true }), }) }) - await expect(fetchData('/test/safe?q=123')).rejects.toThrow('Failed') - expect(fetch).toHaveBeenCalledWith('/test/safe?q=123', undefined) + await expect(deleteData('/test/safe', { TestHeader: '123456' })).resolves.toEqual({ success: true }) + + expect(fetch).toHaveBeenCalledWith('/test/safe', { + method: 'DELETE', + headers: { + TestHeader: '123456', + 'Content-Type': 'application/json', + }, + }) }) }) })