-
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.
Write more unit tests and refactor code
- Loading branch information
Showing
9 changed files
with
250 additions
and
43 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,21 +1,23 @@ | ||
import { appConfig } from '../config'; | ||
|
||
export const fetchAccessTokenUsingRefreshToken = async (refreshToken) => { | ||
export const fetchAccessTokenUsingRefreshToken = async ( | ||
refreshToken: string, | ||
): Promise<string> => { | ||
if (!refreshToken) { | ||
throw new Error('No refresh token available'); | ||
} | ||
|
||
return fetch(`${appConfig.API_URI}/api/auth/refresh`, { | ||
const response = await fetch(`${appConfig.API_URI}/api/auth/refresh`, { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify({ refreshToken }), | ||
}) | ||
.then((response) => response.json()) | ||
.then((data) => { | ||
if (data.accessToken) { | ||
return data.accessToken; | ||
} else { | ||
throw new Error('No access token available'); | ||
} | ||
}); | ||
}); | ||
|
||
const data = await response.json(); | ||
|
||
if (!data.accessToken) { | ||
throw new Error('Could not generate access token using refresh token'); | ||
} | ||
|
||
return data.accessToken; | ||
}; |
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,50 @@ | ||
import { fetchAccessTokenUsingRefreshToken } from './fetchAccessTokenUsingRefreshToken'; | ||
|
||
jest.mock('../config', () => ({ | ||
appConfig: { | ||
API_URI: 'https://example.com', | ||
}, | ||
})); | ||
|
||
describe('fetchAccessTokenUsingRefreshToken', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should throw an error if no refresh token is provided', async () => { | ||
await expect(fetchAccessTokenUsingRefreshToken('')).rejects.toThrow( | ||
'No refresh token available', | ||
); | ||
}); | ||
|
||
it('should return the access token if the request is successful', async () => { | ||
const mockAccessToken = 'new-access-token'; | ||
global.fetch = jest.fn().mockResolvedValue({ | ||
json: jest.fn().mockResolvedValue({ accessToken: mockAccessToken }), | ||
} as any); | ||
|
||
const result = await fetchAccessTokenUsingRefreshToken( | ||
'valid-refresh-token', | ||
); | ||
|
||
expect(result).toBe(mockAccessToken); | ||
}); | ||
|
||
it('should throw an error if the access token is not returned', async () => { | ||
global.fetch = jest.fn().mockResolvedValue({ | ||
json: jest.fn().mockResolvedValue({}), | ||
} as any); | ||
|
||
await expect( | ||
fetchAccessTokenUsingRefreshToken('valid-refresh-token'), | ||
).rejects.toThrow('Could not generate access token using refresh token'); | ||
}); | ||
|
||
it('should throw an error if the fetch call fails', async () => { | ||
global.fetch = jest.fn().mockRejectedValue(new Error('Network error')); | ||
|
||
await expect( | ||
fetchAccessTokenUsingRefreshToken('valid-refresh-token'), | ||
).rejects.toThrow('Network error'); | ||
}); | ||
}); |
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,101 @@ | ||
import { fetchWithToken } from './fetchWithToken'; | ||
|
||
describe('fetchWithToken', () => { | ||
const mockUrl = 'https://api.example.com/data'; | ||
const mockOptions = { method: 'GET' }; | ||
|
||
let mockFetch: jest.Mock; | ||
let mockRefreshAccessToken: jest.Mock; | ||
let mockSetAccessToken: jest.Mock; | ||
|
||
beforeEach(() => { | ||
mockFetch = jest.fn(); | ||
global.fetch = mockFetch; | ||
|
||
mockRefreshAccessToken = jest.fn(); | ||
mockSetAccessToken = jest.fn(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should make a request with the access token', async () => { | ||
mockFetch.mockResolvedValueOnce({ status: 200 } as Response); | ||
|
||
const response = await fetchWithToken({ | ||
url: mockUrl, | ||
options: mockOptions, | ||
accessToken: 'initial-token', | ||
refreshAccessToken: mockRefreshAccessToken, | ||
}); | ||
|
||
expect(mockFetch).toHaveBeenCalledWith(mockUrl, { | ||
...mockOptions, | ||
headers: { Authorization: 'Bearer initial-token' }, | ||
}); | ||
expect(response.status).toBe(200); | ||
}); | ||
|
||
it('should refresh the access token and retry the request if the first request is unauthorized', async () => { | ||
mockFetch | ||
.mockResolvedValueOnce({ status: 401 } as Response) | ||
.mockResolvedValueOnce({ status: 200 } as Response); | ||
mockRefreshAccessToken.mockResolvedValueOnce('new-token'); | ||
|
||
const response = await fetchWithToken({ | ||
url: mockUrl, | ||
options: mockOptions, | ||
accessToken: 'initial-token', | ||
refreshAccessToken: mockRefreshAccessToken, | ||
setAccessToken: mockSetAccessToken, | ||
}); | ||
|
||
expect(mockFetch).toHaveBeenCalledTimes(2); | ||
expect(mockFetch).toHaveBeenNthCalledWith(1, mockUrl, { | ||
...mockOptions, | ||
headers: { Authorization: 'Bearer initial-token' }, | ||
}); | ||
expect(mockFetch).toHaveBeenNthCalledWith(2, mockUrl, { | ||
...mockOptions, | ||
headers: { Authorization: 'Bearer new-token' }, | ||
}); | ||
expect(mockRefreshAccessToken).toHaveBeenCalled(); | ||
expect(mockSetAccessToken).toHaveBeenCalledWith('new-token'); | ||
expect(response.status).toBe(200); | ||
}); | ||
|
||
it('should throw an error if the refresh token process fails', async () => { | ||
mockFetch.mockResolvedValueOnce({ status: 401 } as Response); | ||
mockRefreshAccessToken.mockRejectedValueOnce( | ||
new Error('Refresh token failed'), | ||
); | ||
|
||
await expect( | ||
fetchWithToken({ | ||
url: mockUrl, | ||
options: mockOptions, | ||
accessToken: 'initial-token', | ||
refreshAccessToken: mockRefreshAccessToken, | ||
}), | ||
).rejects.toThrow('Refresh token failed'); | ||
|
||
expect(mockFetch).toHaveBeenCalledTimes(1); | ||
expect(mockRefreshAccessToken).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should use default options if none are provided', async () => { | ||
mockFetch.mockResolvedValueOnce({ status: 200 } as Response); | ||
|
||
const response = await fetchWithToken({ | ||
url: mockUrl, | ||
accessToken: 'initial-token', | ||
refreshAccessToken: mockRefreshAccessToken, | ||
}); | ||
|
||
expect(mockFetch).toHaveBeenCalledWith(mockUrl, { | ||
headers: { Authorization: 'Bearer initial-token' }, | ||
}); | ||
expect(response.status).toBe(200); | ||
}); | ||
}); |
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,34 @@ | ||
import { getCookieValue } from './getCookieValue'; | ||
|
||
describe('getCookieValue', () => { | ||
beforeEach(() => { | ||
// Clear document.cookie before each test | ||
document.cookie = ''; | ||
}); | ||
|
||
it('should return the value of the specified cookie', () => { | ||
document.cookie = 'testCookie=testValue'; | ||
expect(getCookieValue('testCookie')).toBe('testValue'); | ||
}); | ||
|
||
it('should return null if the specified cookie does not exist', () => { | ||
expect(getCookieValue('nonExistentCookie')).toBeNull(); | ||
}); | ||
|
||
it('should return the correct value if multiple cookies are present', () => { | ||
document.cookie = 'cookie1=value1'; | ||
document.cookie = 'cookie2=value2'; | ||
expect(getCookieValue('cookie1')).toBe('value1'); | ||
expect(getCookieValue('cookie2')).toBe('value2'); | ||
}); | ||
|
||
it('should handle cookies with spaces around the name', () => { | ||
document.cookie = ' testCookieWithSpaces = testValue '; | ||
expect(getCookieValue('testCookieWithSpaces')).toBe('testValue'); | ||
}); | ||
|
||
it('should return null if the cookie value is empty', () => { | ||
document.cookie = 'emptyCookie='; | ||
expect(getCookieValue('emptyCookie')).toBe(null); | ||
}); | ||
}); |
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,8 +1,4 @@ | ||
export const getCookieValue = (name: string) => { | ||
export const getCookieValue = (name: string): string | null => { | ||
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); | ||
if (match) { | ||
return match[2]; | ||
} | ||
|
||
return null; | ||
return match ? match[2] : null; | ||
}; |
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,27 @@ | ||
import { nonNullable } from './nonNullable'; | ||
|
||
describe('nonNullable', () => { | ||
it('should return false for null', () => { | ||
expect(nonNullable(null)).toBe(false); | ||
}); | ||
|
||
it('should return false for undefined', () => { | ||
expect(nonNullable(undefined)).toBe(false); | ||
}); | ||
|
||
it('should return true for a non-null value', () => { | ||
expect(nonNullable(0)).toBe(true); | ||
expect(nonNullable('')).toBe(true); | ||
expect(nonNullable(false)).toBe(true); | ||
expect(nonNullable([])).toBe(true); | ||
expect(nonNullable({})).toBe(true); | ||
expect(nonNullable('string')).toBe(true); | ||
expect(nonNullable(123)).toBe(true); | ||
}); | ||
|
||
it('should filter out null and undefined values from an array', () => { | ||
const array = [1, null, 2, undefined, 3]; | ||
const filteredArray = array.filter(nonNullable); | ||
expect(filteredArray).toEqual([1, 2, 3]); | ||
}); | ||
}); |