From 15491e5629af3656578eb632797c18262658e827 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Wed, 11 Sep 2024 13:41:33 -0400 Subject: [PATCH] fix: ds-356 axios response interceptors --- .../__snapshots__/useLoginApi.test.ts.snap | 12 ++++-- src/hooks/__tests__/useLoginApi.test.ts | 42 ++++++++++++++----- src/hooks/useLoginApi.ts | 33 +++++++++++---- tests/__snapshots__/code.test.ts.snap | 2 +- 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/hooks/__tests__/__snapshots__/useLoginApi.test.ts.snap b/src/hooks/__tests__/__snapshots__/useLoginApi.test.ts.snap index 2479254a..de8469f6 100644 --- a/src/hooks/__tests__/__snapshots__/useLoginApi.test.ts.snap +++ b/src/hooks/__tests__/__snapshots__/useLoginApi.test.ts.snap @@ -8,9 +8,7 @@ exports[`useGetSetAuthApi should attempt a call to get a token: getToken 1`] = ` ] `; -exports[`useGetSetAuthApi should process an interceptor error: interceptorError 1`] = `undefined`; - -exports[`useGetSetAuthApi should process an interceptor success: interceptorSuccess 1`] = ` +exports[`useGetSetAuthApi should process an interceptor request success: interceptorRequestSuccess 1`] = ` { "headers": { "Authorization": "Token Dolor sit", @@ -19,7 +17,7 @@ exports[`useGetSetAuthApi should process an interceptor success: interceptorSucc } `; -exports[`useGetSetAuthApi should process an interceptor success: interceptorSuccess, token service 1`] = ` +exports[`useGetSetAuthApi should process an interceptor request success: interceptorRequestSuccess, token service 1`] = ` { "headers": { "Authorization": "", @@ -28,6 +26,12 @@ exports[`useGetSetAuthApi should process an interceptor success: interceptorSucc } `; +exports[`useGetSetAuthApi should process an interceptor response success: interceptorResponseSuccess 1`] = `undefined`; + +exports[`useGetSetAuthApi should process interceptor request errors: interceptorRequestError 1`] = `undefined`; + +exports[`useGetSetAuthApi should process interceptor response errors: interceptorResponseError 1`] = `undefined`; + exports[`useLoginApi should attempt an api call to login: apiCall 1`] = ` [ [ diff --git a/src/hooks/__tests__/useLoginApi.test.ts b/src/hooks/__tests__/useLoginApi.test.ts index eadfd8ec..b43f270a 100644 --- a/src/hooks/__tests__/useLoginApi.test.ts +++ b/src/hooks/__tests__/useLoginApi.test.ts @@ -179,10 +179,17 @@ describe('useUserApi', () => { }); describe('useGetSetAuthApi', () => { + const mockLogoutCallback = jest.fn(); let hookResult; beforeEach(() => { - const hook = renderHook(() => useGetSetAuthApi()); + const mockLogoutApi = () => ({ + callbackSuccess: mockLogoutCallback, + callbackError: jest.fn(), + apiCall: jest.fn(), + logout: jest.fn() + }); + const hook = renderHook(() => useGetSetAuthApi({ useLogout: mockLogoutApi })); hookResult = hook?.result?.current; }); @@ -198,24 +205,39 @@ describe('useGetSetAuthApi', () => { expect(spyCookie.mock.calls).toMatchSnapshot('getToken'); }); - it('should process an interceptor success', () => { - const { interceptorSuccess } = hookResult; + it('should process an interceptor request success', async () => { + const { interceptorRequestSuccess } = hookResult; jest.spyOn(cookies, 'get').mockReturnValueOnce('RG9sb3Igc2l0'); const mockConfig = { headers: {}, url: '//mock-url' }; - act(() => interceptorSuccess(mockConfig)); - expect(mockConfig).toMatchSnapshot('interceptorSuccess'); + act(() => interceptorRequestSuccess(mockConfig)); + expect(mockConfig).toMatchSnapshot('interceptorRequestSuccess'); jest.spyOn(cookies, 'get').mockReturnValueOnce(''); const mockConfigTokenService = { headers: {}, url: `${process.env.REACT_APP_USER_SERVICE_AUTH_TOKEN}` }; - act(() => interceptorSuccess(mockConfigTokenService)); - expect(mockConfigTokenService).toMatchSnapshot('interceptorSuccess, token service'); + act(() => interceptorRequestSuccess(mockConfigTokenService)); + expect(mockConfigTokenService).toMatchSnapshot('interceptorRequestSuccess, token service'); + }); + + it('should process an interceptor response success', async () => { + const { interceptorResponseSuccess } = hookResult; + + await expect(interceptorResponseSuccess()).toMatchSnapshot('interceptorResponseSuccess'); }); - it('should process an interceptor error', async () => { - const { interceptorError } = hookResult; + it('should process interceptor request errors', async () => { + const { interceptorRequestError } = hookResult; + + await expect(interceptorRequestError()).rejects.toMatchSnapshot('interceptorRequestError'); + }); + + it('should process interceptor response errors', async () => { + const { interceptorResponseError } = hookResult; + + await expect(interceptorResponseError()).rejects.toMatchSnapshot('interceptorResponseError'); - await expect(interceptorError()).rejects.toMatchSnapshot('interceptorError'); + await interceptorResponseError({ response: { status: 401 } }); + expect(mockLogoutCallback).toHaveBeenCalledTimes(1); }); it('should return an authorization value on mount', async () => { diff --git a/src/hooks/useLoginApi.ts b/src/hooks/useLoginApi.ts index 1d21ac9e..506a5e2e 100644 --- a/src/hooks/useLoginApi.ts +++ b/src/hooks/useLoginApi.ts @@ -151,7 +151,8 @@ const useUserApi = () => { /** * Get initial token. Apply and set token for all Axios request interceptors, global authorization */ -const useGetSetAuthApi = () => { +const useGetSetAuthApi = ({ useLogout = useLogoutApi }: { useLogout?: typeof useLogoutApi } = {}) => { + const { callbackSuccess: revokeToken } = useLogout(); const [isAuthorized, setIsAuthorized] = useState(false); const getToken = useCallback(() => { @@ -176,7 +177,7 @@ const useGetSetAuthApi = () => { return parsedToken; }, []); - const interceptorSuccess = useCallback( + const interceptorRequestSuccess = useCallback( config => { const headerToken = getToken(); @@ -194,13 +195,27 @@ const useGetSetAuthApi = () => { [getToken] ); - const interceptorError = useCallback(error => Promise.reject(error), []); + const interceptorRequestError = useCallback(error => Promise.reject(error), []); - const setInterceptors = useCallback( - () => axios.interceptors.request.use(interceptorSuccess, interceptorError), - [interceptorError, interceptorSuccess] + const interceptorResponseSuccess = useCallback(config => config, []); + + const interceptorResponseError = useCallback( + error => { + if (error?.response?.status === 401) { + setIsAuthorized(false); + return revokeToken(); + } + + return Promise.reject(error); + }, + [revokeToken] ); + const setInterceptors = useCallback(() => { + axios.interceptors.request.use(interceptorRequestSuccess, interceptorRequestError); + axios.interceptors.response.use(interceptorResponseSuccess, interceptorResponseError); + }, [interceptorResponseError, interceptorResponseSuccess, interceptorRequestError, interceptorRequestSuccess]); + useEffect(() => { setInterceptors(); getToken(); @@ -210,8 +225,10 @@ const useGetSetAuthApi = () => { return { getToken, isAuthorized, - interceptorError, - interceptorSuccess, + interceptorResponseError, + interceptorResponseSuccess, + interceptorRequestError, + interceptorRequestSuccess, setInterceptors }; }; diff --git a/tests/__snapshots__/code.test.ts.snap b/tests/__snapshots__/code.test.ts.snap index ac3634fd..43598f8e 100644 --- a/tests/__snapshots__/code.test.ts.snap +++ b/tests/__snapshots__/code.test.ts.snap @@ -14,7 +14,7 @@ exports[`General code checks should only have specific console.[warn|log|info|er "hooks/useLoginApi.ts:58: console.error(error);", "hooks/useLoginApi.ts:99: console.error(error);", "hooks/useLoginApi.ts:137: console.error(error);", - "hooks/useLoginApi.ts:165: console.error('Invalid token, unable to parse format');", + "hooks/useLoginApi.ts:166: console.error('Invalid token, unable to parse format');", "hooks/useScanApi.ts:67: console.error(error);", "hooks/useScanApi.ts:140: console.error(error);", "hooks/useScanApi.ts:186: console.log(missingScansMsg);",