Skip to content

Commit

Permalink
fix: Pass headers for GET and DELETE requests (#149)
Browse files Browse the repository at this point in the history
  • Loading branch information
usame-algan authored Feb 22, 2024
1 parent e7f7d64 commit a723748
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 49 deletions.
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',
},
})
})
})
})

0 comments on commit a723748

Please sign in to comment.