UNPKG

@appello/services

Version:

Services package with api / graphql

236 lines (235 loc) 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const refreshTokens_1 = require("./refreshTokens"); jest.mock('../setAuthorizationHeader', () => ({ setAuthorizationHeader: jest.fn((token, headers) => (Object.assign(Object.assign({}, headers), { Authorization: `Bearer ${token}` }))), })); const createMockAxiosError = (config = {}) => { const error = new Error('Mock axios error'); error.isAxiosError = true; error.config = Object.assign({ url: '/test', method: 'GET', headers: {} }, config); return error; }; const createMockAxiosInstance = () => { const mockInstance = jest.fn(() => Promise.resolve({ data: 'success' })); mockInstance.get = jest.fn(); mockInstance.post = jest.fn(); mockInstance.put = jest.fn(); mockInstance.patch = jest.fn(); mockInstance.delete = jest.fn(); mockInstance.request = jest.fn(); mockInstance.defaults = {}; mockInstance.interceptors = { request: { use: jest.fn(), eject: jest.fn() }, response: { use: jest.fn(), eject: jest.fn() }, }; mockInstance.mockImplementation(() => Promise.resolve({ data: 'success' })); mockInstance.request.mockImplementation(() => Promise.resolve({ data: 'success' })); mockInstance.post.mockImplementation(() => Promise.resolve({ data: { accessToken: 'token' } })); return mockInstance; }; describe('refreshTokens', () => { let mockInstance; let mockConfig; let mockError; beforeEach(() => { mockInstance = createMockAxiosInstance(); mockConfig = { url: 'https://api.test.com', getAccessToken: jest.fn().mockResolvedValue('old-access-token'), getRefreshToken: jest.fn().mockResolvedValue('refresh-token'), onTokenRefreshSuccess: jest.fn(), onTokenRefreshError: jest.fn(), refreshTokenUrl: '/auth/refresh', }; mockError = createMockAxiosError({ headers: { 'Content-Type': 'application/json' }, }); jest.resetModules(); global.isRefreshing = false; global.refreshQueues = []; }); afterEach(() => { jest.clearAllMocks(); }); describe('Custom refresh token function', () => { it('should use custom refreshTokens function when provided', async () => { const customRefreshTokens = jest.fn().mockResolvedValue({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', }); mockConfig.refreshTokens = customRefreshTokens; const result = await (0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig); expect(customRefreshTokens).toHaveBeenCalledWith(mockInstance, mockError); expect(mockConfig.onTokenRefreshSuccess).toHaveBeenCalledWith({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', }); expect(mockInstance).toHaveBeenCalled(); expect(result).toEqual({ data: 'success' }); }); it('should handle custom refresh token function failure', async () => { const customError = new Error('Custom refresh failed'); mockConfig.refreshTokens = jest.fn().mockRejectedValue(customError); await expect((0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig)).rejects.toThrow('Custom refresh failed'); expect(mockConfig.onTokenRefreshError).toHaveBeenCalledWith(customError); expect(mockConfig.onTokenRefreshSuccess).not.toHaveBeenCalled(); }); }); describe('Default refresh token endpoint', () => { it('should use default refresh endpoint when custom function not provided', async () => { mockInstance.post.mockResolvedValue({ data: { accessToken: 'new-access-token', refreshToken: 'new-refresh-token', }, }); const result = await (0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig); expect(mockInstance.post).toHaveBeenCalledWith('/auth/refresh', { refreshToken: 'refresh-token', }); expect(mockConfig.onTokenRefreshSuccess).toHaveBeenCalledWith({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', }); expect(result).toEqual({ data: 'success' }); }); it('should handle default refresh endpoint failure', async () => { const refreshError = new Error('Refresh endpoint failed'); mockInstance.post.mockRejectedValue(refreshError); await expect((0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig)).rejects.toThrow('Refresh endpoint failed'); expect(mockConfig.onTokenRefreshError).toHaveBeenCalledWith(refreshError); expect(mockConfig.onTokenRefreshSuccess).not.toHaveBeenCalled(); }); it('should use empty string as default refresh URL', async () => { delete mockConfig.refreshTokenUrl; mockInstance.post.mockResolvedValue({ data: { accessToken: 'new-token' }, }); await (0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig); expect(mockInstance.post).toHaveBeenCalledWith('', { refreshToken: 'refresh-token', }); }); }); describe('Token extraction and header setting', () => { it('should use getAccessTokenFromRefreshRequest when provided', async () => { var _a; const customTokenExtractor = jest.fn().mockReturnValue('extracted-token'); mockConfig.getAccessTokenFromRefreshRequest = customTokenExtractor; mockConfig.refreshTokens = jest.fn().mockResolvedValue({ customField: 'custom-data', accessToken: 'default-token', }); await (0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig); expect(customTokenExtractor).toHaveBeenCalledWith({ customField: 'custom-data', accessToken: 'default-token', }); const requestCall = mockInstance.mock .calls[0][0]; expect((_a = requestCall.headers) === null || _a === void 0 ? void 0 : _a.Authorization).toBe('Bearer extracted-token'); }); it('should fallback to data.accessToken when custom extractor not provided', async () => { var _a; mockConfig.refreshTokens = jest.fn().mockResolvedValue({ accessToken: 'fallback-token', otherField: 'other-data', }); await (0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig); const requestCall = mockInstance.mock .calls[0][0]; expect((_a = requestCall.headers) === null || _a === void 0 ? void 0 : _a.Authorization).toBe('Bearer fallback-token'); }); }); describe('Request configuration handling', () => { it('should set refreshRetry flag on the request config', async () => { mockConfig.refreshTokens = jest.fn().mockResolvedValue({ accessToken: 'new-token', }); await (0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig); const requestCall = mockInstance.mock .calls[0][0]; expect(requestCall.refreshRetry).toBe(true); }); it('should preserve original request configuration', async () => { var _a, _b; const originalConfig = { url: '/test-endpoint', method: 'POST', data: { test: 'data' }, headers: { 'Custom-Header': 'custom-value' }, }; mockError = createMockAxiosError(originalConfig); mockConfig.refreshTokens = jest.fn().mockResolvedValue({ accessToken: 'new-token', }); await (0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig); const requestCall = mockInstance.mock .calls[0][0]; expect(requestCall.url).toBe('/test-endpoint'); expect(requestCall.method).toBe('POST'); expect(requestCall.data).toEqual({ test: 'data' }); expect((_a = requestCall.headers) === null || _a === void 0 ? void 0 : _a['Custom-Header']).toBe('custom-value'); expect((_b = requestCall.headers) === null || _b === void 0 ? void 0 : _b.Authorization).toBe('Bearer new-token'); }); }); describe('Callback handling', () => { it('should call onTokenRefreshSuccess with new tokens', async () => { const newTokens = { accessToken: 'new-access-token', refreshToken: 'new-refresh-token', expiresIn: 3600, }; mockConfig.refreshTokens = jest.fn().mockResolvedValue(newTokens); await (0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig); expect(mockConfig.onTokenRefreshSuccess).toHaveBeenCalledWith(newTokens); }); it('should call onTokenRefreshError on failure', async () => { const refreshError = new Error('Token refresh failed'); mockConfig.refreshTokens = jest.fn().mockRejectedValue(refreshError); await expect((0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig)).rejects.toThrow('Token refresh failed'); expect(mockConfig.onTokenRefreshError).toHaveBeenCalledWith(refreshError); }); it('should handle missing callback functions gracefully', async () => { const configWithoutCallbacks = Object.assign({}, mockConfig); delete configWithoutCallbacks.onTokenRefreshSuccess; delete configWithoutCallbacks.onTokenRefreshError; configWithoutCallbacks.refreshTokens = jest.fn().mockResolvedValue({ accessToken: 'new-token', }); await expect((0, refreshTokens_1.refreshTokens)(mockInstance, mockError, configWithoutCallbacks)).resolves.toBeDefined(); }); }); describe('Generic type handling', () => { it('should work with custom token types', async () => { const customConfig = Object.assign(Object.assign({}, mockConfig), { refreshTokens: jest.fn().mockResolvedValue({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', customField: 'custom-value', }) }); const result = await (0, refreshTokens_1.refreshTokens)(mockInstance, mockError, customConfig); expect(customConfig.onTokenRefreshSuccess).toHaveBeenCalledWith({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', customField: 'custom-value', }); expect(result).toBeDefined(); }); }); describe('Error propagation', () => { it('should re-throw refresh errors after handling', async () => { const originalError = new Error('Original refresh error'); mockConfig.refreshTokens = jest.fn().mockRejectedValue(originalError); await expect((0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig)).rejects.toBe(originalError); }); it('should handle errors in the finally block', async () => { mockConfig.refreshTokens = jest.fn().mockResolvedValue({ accessToken: 'new-token', }); mockInstance.mockRejectedValue(new Error('Request failed')); await expect((0, refreshTokens_1.refreshTokens)(mockInstance, mockError, mockConfig)).rejects.toThrow('Request failed'); expect(mockConfig.onTokenRefreshSuccess).toHaveBeenCalled(); }); }); });