-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(apiRequest): simplify logic and remove error handling depend…
…ency - Remove handleApiError dependency and its test - Add TypeDoc documentation for apiRequest - Add simple error handling inside apiRequest
- Loading branch information
Showing
4 changed files
with
62 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
import { handleApiError } from '../utils/errorHandling'; | ||
|
||
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE'; | ||
|
||
export interface ApiOptions { | ||
|
@@ -17,21 +15,37 @@ const defaultOptions: ApiOptions = { | |
credentials: 'include', | ||
}; | ||
|
||
/** | ||
* Makes an API request to the specified URL with the given options. | ||
* | ||
* @param {string} url - The endpoint to call, relative to `API_BASE_URL`. | ||
* @param {ApiOptions} [options=defaultOptions] - Options for configuring the request, | ||
* including HTTP method, headers, request body, and credentials. Defaults to `defaultOptions`. | ||
* @returns {Promise<T>} A promise resolving to the parsed response data of type `T`. | ||
* @throws {Error} Throws an error if the network response is not OK or if the request fails. | ||
* The error contains the response body as a JSON string if available. | ||
* @example | ||
* // Example usage of a POST request | ||
* const result = await apiRequest<{ message: string }>('/access/register', { | ||
* method: 'POST', | ||
* body: { email: '[email protected]', password: 'securePassword' }, | ||
* }); | ||
* console.log(result.message); | ||
**/ | ||
|
||
export const apiRequest = async <T>( | ||
url: string, | ||
options: ApiOptions = defaultOptions | ||
): Promise<T> => { | ||
try { | ||
const response = await fetch(`${API_BASE_URL}${url}`, { | ||
...(options as RequestInit), | ||
body: options.body && JSON.stringify(options.body), | ||
}); | ||
const response = await fetch(`${API_BASE_URL}${url}`, { | ||
...(options as RequestInit), | ||
body: options.body && JSON.stringify(options.body), | ||
}); | ||
|
||
return await handleApiError<T>(response); | ||
} catch (error) { | ||
if (error instanceof Error && 'flash' in error) { | ||
throw error; | ||
} | ||
throw new Error('Unexpected API Error'); | ||
if (!response.ok) { | ||
const errorResponse = await response.json(); | ||
throw new Error(JSON.stringify(errorResponse)); | ||
} | ||
|
||
return response.json(); | ||
}; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,121 +1,113 @@ | ||
import { ApiOptions, apiRequest } from '../../../src/hooks/apiRequest'; | ||
import { handleApiError } from '../../../src/utils/errorHandling'; | ||
|
||
global.fetch = jest.fn(); | ||
|
||
jest.mock('../../../src/utils/errorHandling', () => ({ | ||
handleApiError: jest.fn(), | ||
})); | ||
|
||
describe('apiRequest', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should use default options for a basic request', async () => { | ||
it('should use default options for a basic GET request', async () => { | ||
const mockResponse = { data: 'test data' }; | ||
(handleApiError as jest.Mock).mockResolvedValueOnce(mockResponse); | ||
(global.fetch as jest.Mock).mockResolvedValueOnce({ | ||
ok: true, | ||
json: () => Promise.resolve(mockResponse), | ||
json: jest.fn().mockResolvedValueOnce(mockResponse), | ||
}); | ||
|
||
await apiRequest('/test-endpoint'); | ||
const response = await apiRequest('/test-endpoint'); | ||
|
||
const fetchCalls = (global.fetch as jest.Mock).mock.calls; | ||
|
||
expect(fetchCalls[0][0]).toBe( | ||
`${process.env.NEXT_PUBLIC_BACKEND_URL}/test-endpoint` | ||
); | ||
expect(fetchCalls[0][1]).toMatchObject({ | ||
method: undefined, | ||
headers: { 'Content-Type': 'application/json' }, | ||
credentials: 'include', | ||
}); | ||
expect(response).toEqual(mockResponse); | ||
}); | ||
|
||
it('should override default options with custom options', async () => { | ||
const mockResponse = { data: 'test data' }; | ||
(handleApiError as jest.Mock).mockResolvedValueOnce(mockResponse); | ||
const mockResponse = { data: 'custom data' }; | ||
(global.fetch as jest.Mock).mockResolvedValueOnce({ | ||
ok: true, | ||
json: () => Promise.resolve(mockResponse), | ||
json: jest.fn().mockResolvedValueOnce(mockResponse), | ||
}); | ||
|
||
const customOptions: ApiOptions = { | ||
method: 'POST', | ||
headers: { | ||
Authorization: 'Bearer token', | ||
'Content-Type': 'application/json', | ||
}, | ||
body: { name: 'test' }, | ||
credentials: 'include', | ||
}; | ||
|
||
await apiRequest('/test-endpoint', customOptions); | ||
|
||
const fetchCalls = (global.fetch as jest.Mock).mock.calls; | ||
|
||
expect(fetchCalls[0][1]).toMatchObject(customOptions); | ||
expect(fetchCalls[0][1]).toMatchObject({ | ||
method: 'POST', | ||
headers: { | ||
Authorization: 'Bearer token', | ||
'Content-Type': 'application/json', | ||
}, | ||
credentials: 'include', | ||
body: JSON.stringify({ name: 'test' }), | ||
}); | ||
}); | ||
|
||
it('should make a successful POST request', async () => { | ||
const mockResponse = { data: 'created' }; | ||
const requestBody = { name: 'test' }; | ||
it('should make a successful POST request and return the correct response', async () => { | ||
const mockResponse = { message: 'Success' }; | ||
const requestBody = { email: 'test@example.com', password: 'password123' }; | ||
|
||
(handleApiError as jest.Mock).mockResolvedValueOnce(mockResponse); | ||
(global.fetch as jest.Mock).mockResolvedValueOnce({ | ||
ok: true, | ||
json: () => Promise.resolve(mockResponse), | ||
json: jest.fn().mockResolvedValueOnce(mockResponse), | ||
}); | ||
|
||
const options: ApiOptions = { | ||
method: 'POST', | ||
body: requestBody, | ||
headers: { 'Content-Type': 'application/json' }, | ||
credentials: 'include', | ||
}; | ||
|
||
const result = await apiRequest('/create', options); | ||
const response = await apiRequest('/register', options); | ||
|
||
expect(response).toEqual(mockResponse); | ||
|
||
const fetchCalls = (global.fetch as jest.Mock).mock.calls; | ||
|
||
expect(fetchCalls[0][1]).toMatchObject({ | ||
method: 'POST', | ||
body: JSON.stringify(requestBody), | ||
headers: { 'Content-Type': 'application/json' }, | ||
credentials: 'include', | ||
}); | ||
expect(result).toEqual(mockResponse); | ||
}); | ||
|
||
it('should handle API errors', async () => { | ||
const errorResponse = { | ||
flash: { | ||
error: ['Invalid request'], | ||
}, | ||
}; | ||
|
||
const apiError = new Error('Invalid request'); | ||
Object.assign(apiError, { | ||
flash: errorResponse.flash, | ||
statusCode: 400, | ||
}); | ||
it('should throw an error for non-OK responses', async () => { | ||
const errorResponse = { message: 'Error occurred' }; | ||
|
||
(global.fetch as jest.Mock).mockResolvedValueOnce({ | ||
ok: false, | ||
status: 400, | ||
json: () => Promise.resolve(errorResponse), | ||
json: jest.fn().mockResolvedValueOnce(errorResponse), | ||
}); | ||
|
||
(handleApiError as jest.Mock).mockRejectedValueOnce(apiError); | ||
|
||
await expect(apiRequest('/test-endpoint')).rejects.toThrow( | ||
'Invalid request' | ||
await expect(apiRequest('/error-endpoint')).rejects.toThrow( | ||
JSON.stringify(errorResponse) | ||
); | ||
}); | ||
|
||
it('should handle network errors', async () => { | ||
it('should handle network errors gracefully', async () => { | ||
(global.fetch as jest.Mock).mockRejectedValueOnce( | ||
new Error('Network error') | ||
); | ||
|
||
await expect(apiRequest('/test-endpoint')).rejects.toThrow( | ||
'Unexpected API Error' | ||
); | ||
await expect(apiRequest('/test-endpoint')).rejects.toThrow('Network error'); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.