-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add NodeHttpClient and FetchHttpClient tests for retry logic (#1154)
## Description - Adds tests for `NodeHttpClient` and `FetchHttpClient` to test network retries for the FGA module - Removes retry test cases from FGA module unit tests ## Documentation Does this require changes to the WorkOS Docs? E.g. the [API Reference](https://workos.com/docs/reference) or code snippets need updates. ``` [ ] Yes ``` If yes, link a related docs PR and add a docs maintainer as a reviewer. Their approval is required.
- Loading branch information
1 parent
4079e10
commit d6e32e1
Showing
4 changed files
with
427 additions
and
692 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
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 |
---|---|---|
@@ -0,0 +1,227 @@ | ||
import fetch from 'jest-fetch-mock'; | ||
import { fetchOnce, fetchURL } from '../../common/utils/test-utils'; | ||
import { FetchHttpClient } from './fetch-client'; | ||
|
||
const fetchClient = new FetchHttpClient('https://test.workos.com', { | ||
headers: { | ||
Authorization: `Bearer sk_test`, | ||
'User-Agent': 'test-fetch-client', | ||
}, | ||
}); | ||
|
||
describe('Fetch client', () => { | ||
beforeEach(() => fetch.resetMocks()); | ||
|
||
describe('fetchRequestWithRetry', () => { | ||
it('get for FGA path should call fetchRequestWithRetry and return response', async () => { | ||
fetchOnce({ data: 'response' }); | ||
const mockFetchRequestWithRetry = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'fetchRequestWithRetry', | ||
); | ||
|
||
const response = await fetchClient.get('/fga/v1/resources', {}); | ||
|
||
expect(mockFetchRequestWithRetry).toHaveBeenCalledTimes(1); | ||
expect(fetchURL()).toBe('https://test.workos.com/fga/v1/resources'); | ||
expect(await response.toJSON()).toEqual({ data: 'response' }); | ||
}); | ||
|
||
it('post for FGA path should call fetchRequestWithRetry and return response', async () => { | ||
fetchOnce({ data: 'response' }); | ||
const mockFetchRequestWithRetry = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'fetchRequestWithRetry', | ||
); | ||
|
||
const response = await fetchClient.post('/fga/v1/resources', {}, {}); | ||
|
||
expect(mockFetchRequestWithRetry).toHaveBeenCalledTimes(1); | ||
expect(fetchURL()).toBe('https://test.workos.com/fga/v1/resources'); | ||
expect(await response.toJSON()).toEqual({ data: 'response' }); | ||
}); | ||
|
||
it('put for FGA path should call fetchRequestWithRetry and return response', async () => { | ||
fetchOnce({ data: 'response' }); | ||
const mockFetchRequestWithRetry = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'fetchRequestWithRetry', | ||
); | ||
|
||
const response = await fetchClient.put( | ||
'/fga/v1/resources/user/user-1', | ||
{}, | ||
{}, | ||
); | ||
|
||
expect(mockFetchRequestWithRetry).toHaveBeenCalledTimes(1); | ||
expect(fetchURL()).toBe( | ||
'https://test.workos.com/fga/v1/resources/user/user-1', | ||
); | ||
expect(await response.toJSON()).toEqual({ data: 'response' }); | ||
}); | ||
|
||
it('delete for FGA path should call fetchRequestWithRetry and return response', async () => { | ||
fetchOnce({ data: 'response' }); | ||
const mockFetchRequestWithRetry = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'fetchRequestWithRetry', | ||
); | ||
|
||
const response = await fetchClient.delete( | ||
'/fga/v1/resources/user/user-1', | ||
{}, | ||
); | ||
|
||
expect(mockFetchRequestWithRetry).toHaveBeenCalledTimes(1); | ||
expect(fetchURL()).toBe( | ||
'https://test.workos.com/fga/v1/resources/user/user-1', | ||
); | ||
expect(await response.toJSON()).toEqual({ data: 'response' }); | ||
}); | ||
|
||
it('should retry request on 500 status code', async () => { | ||
fetchOnce( | ||
{}, | ||
{ | ||
status: 500, | ||
}, | ||
); | ||
fetchOnce({ data: 'response' }); | ||
const mockShouldRetryRequest = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'shouldRetryRequest', | ||
); | ||
const mockSleep = jest.spyOn(fetchClient, 'sleep'); | ||
mockSleep.mockImplementation(() => Promise.resolve()); | ||
|
||
const response = await fetchClient.get('/fga/v1/resources', {}); | ||
|
||
expect(mockShouldRetryRequest).toHaveBeenCalledTimes(2); | ||
expect(mockSleep).toHaveBeenCalledTimes(1); | ||
expect(await response.toJSON()).toEqual({ data: 'response' }); | ||
}); | ||
|
||
it('should retry request on 502 status code', async () => { | ||
fetchOnce( | ||
{}, | ||
{ | ||
status: 502, | ||
}, | ||
); | ||
fetchOnce({ data: 'response' }); | ||
const mockShouldRetryRequest = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'shouldRetryRequest', | ||
); | ||
const mockSleep = jest.spyOn(fetchClient, 'sleep'); | ||
mockSleep.mockImplementation(() => Promise.resolve()); | ||
|
||
const response = await fetchClient.get('/fga/v1/resources', {}); | ||
|
||
expect(mockShouldRetryRequest).toHaveBeenCalledTimes(2); | ||
expect(mockSleep).toHaveBeenCalledTimes(1); | ||
expect(await response.toJSON()).toEqual({ data: 'response' }); | ||
}); | ||
|
||
it('should retry request on 504 status code', async () => { | ||
fetchOnce( | ||
{}, | ||
{ | ||
status: 504, | ||
}, | ||
); | ||
fetchOnce({ data: 'response' }); | ||
const mockShouldRetryRequest = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'shouldRetryRequest', | ||
); | ||
const mockSleep = jest.spyOn(fetchClient, 'sleep'); | ||
mockSleep.mockImplementation(() => Promise.resolve()); | ||
|
||
const response = await fetchClient.get('/fga/v1/resources', {}); | ||
|
||
expect(mockShouldRetryRequest).toHaveBeenCalledTimes(2); | ||
expect(mockSleep).toHaveBeenCalledTimes(1); | ||
expect(await response.toJSON()).toEqual({ data: 'response' }); | ||
}); | ||
|
||
it('should retry request up to 3 times on retryable status code', async () => { | ||
fetchOnce( | ||
{}, | ||
{ | ||
status: 500, | ||
}, | ||
); | ||
fetchOnce( | ||
{}, | ||
{ | ||
status: 502, | ||
}, | ||
); | ||
fetchOnce( | ||
{}, | ||
{ | ||
status: 504, | ||
}, | ||
); | ||
fetchOnce( | ||
{}, | ||
{ | ||
status: 504, | ||
}, | ||
); | ||
const mockShouldRetryRequest = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'shouldRetryRequest', | ||
); | ||
const mockSleep = jest.spyOn(fetchClient, 'sleep'); | ||
mockSleep.mockImplementation(() => Promise.resolve()); | ||
|
||
await expect( | ||
fetchClient.get('/fga/v1/resources', {}), | ||
).rejects.toThrowError('Gateway Timeout'); | ||
|
||
expect(mockShouldRetryRequest).toHaveBeenCalledTimes(4); | ||
expect(mockSleep).toHaveBeenCalledTimes(3); | ||
}); | ||
|
||
it('should not retry requests and throw error with non-retryable status code', async () => { | ||
fetchOnce( | ||
{}, | ||
{ | ||
status: 400, | ||
}, | ||
); | ||
const mockShouldRetryRequest = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'shouldRetryRequest', | ||
); | ||
|
||
await expect( | ||
fetchClient.get('/fga/v1/resources', {}), | ||
).rejects.toThrowError('Bad Request'); | ||
|
||
expect(mockShouldRetryRequest).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should retry request on TypeError', async () => { | ||
fetchOnce({ data: 'response' }); | ||
const mockFetchRequest = jest.spyOn( | ||
FetchHttpClient.prototype as any, | ||
'fetchRequest', | ||
); | ||
mockFetchRequest.mockImplementationOnce(() => { | ||
throw new TypeError('Network request failed'); | ||
}); | ||
const mockSleep = jest.spyOn(fetchClient, 'sleep'); | ||
mockSleep.mockImplementation(() => Promise.resolve()); | ||
|
||
const response = await fetchClient.get('/fga/v1/resources', {}); | ||
|
||
expect(mockFetchRequest).toHaveBeenCalledTimes(2); | ||
expect(mockSleep).toHaveBeenCalledTimes(1); | ||
expect(await response.toJSON()).toEqual({ data: 'response' }); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.