Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/endpoint.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -37,10 +37,10 @@ export function getEndpoint<T extends keyof paths>(
rawUrl?: string,
): Promise<paths[T] extends GetEndpoint ? paths[T]['get']['responses'][200]['schema'] : never> {
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<T extends keyof paths>(
Expand All @@ -49,5 +49,5 @@ export function deleteEndpoint<T extends keyof paths>(
params?: paths[T] extends DeleteEndpoint ? paths[T]['delete']['parameters'] : never,
): Promise<paths[T] extends DeleteEndpoint ? paths[T]['delete']['responses'][200]['schema'] : never> {
const url = makeUrl(baseUrl, path as string, params?.path)
return deleteData(url)
return deleteData(url, params?.headers)
}
39 changes: 29 additions & 10 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,12 @@ async function parseResponse<T>(resp: Response): Promise<T> {

export async function fetchData<T>(
url: string,
method?: 'POST' | 'PUT',
method: 'POST' | 'PUT',
body?: unknown,
headers?: Record<string, string>,
): Promise<T> {
let options:
| {
method: 'POST' | 'PUT'
headers: Record<string, string>
body: string
}
| undefined
let options: RequestInit | undefined

if (body != null) {
const requestHeaders: Record<string, string> = headers ?? {}
requestHeaders['Content-Type'] = 'application/json'
Expand All @@ -84,11 +79,35 @@ export async function fetchData<T>(
return parseResponse<T>(resp)
}

export async function deleteData<T>(url: string): Promise<T> {
const options = {
export async function getData<T>(url: string, headers?: Record<string, string>): Promise<T> {
const options: RequestInit = {
method: 'GET',
}

if (headers) {
options['headers'] = {
...headers,
'Content-Type': 'application/json',
}
}

const resp = await fetch(url, options)

return parseResponse<T>(resp)
}

export async function deleteData<T>(url: string, headers?: Record<string, string>): Promise<T> {
const options: RequestInit = {
method: 'DELETE',
}

if (headers) {
options['headers'] = {
...headers,
'Content-Type': 'application/json',
}
}

const resp = await fetch(url, options)

return parseResponse<T>(resp)
Expand Down
25 changes: 7 additions & 18 deletions tests/endpoint.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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 })),
}
})

Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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,
)
})

Expand Down Expand Up @@ -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 () => {
Expand Down
97 changes: 80 additions & 17 deletions tests/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fetchData, insertParams, stringifyQuery } from '../src/utils'
/// <reference lib="dom" />
import { fetchData, getData, deleteData, insertParams, stringifyQuery } from '../src/utils'

const fetchMock = jest.spyOn(global, 'fetch') as typeof fetch & jest.Mock

Expand Down Expand Up @@ -28,7 +29,7 @@ describe('utils', () => {
})
})

describe('fetchData', () => {
describe('getData', () => {
it('should fetch a simple url', async () => {
fetchMock.mockImplementation(() => {
return Promise.resolve({
Expand All @@ -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({
Expand All @@ -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,
Expand Down Expand Up @@ -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',
},
})
})
})
})