From 953443381648b516cea9f24329cdeb9c53a9faff Mon Sep 17 00:00:00 2001 From: Zak Burke Date: Wed, 27 Nov 2024 11:43:58 -0500 Subject: [PATCH] STCOR-906 consume react-query QueryClient in (#1567) Correctly pass all three required arguments from `` to `loginServices::logout()`: gateway URL, redux store, react-query QueryClient. The third was missing, resulting in an NPE during `logout()` that caused flow to skip to the `finally` clause, skipping the code that cleared localforage. Sometimes localforage appears to get cleared anyway, but sometimes not, in which case the session may fail to terminate even when the fixed-length session timer reaches 00:00. This is one of those "How did this ever work?!? And how did we not notice?!?" situations. Refs [STCOR-906](https://folio-org.atlassian.net/browse/STCOR-906) --- CHANGELOG.md | 1 + src/components/Logout/Logout.js | 4 +++- src/components/Logout/Logout.test.js | 1 + src/loginServices.js | 4 +++- src/loginServices.test.js | 25 +++++++++++++++++++++++++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2f461b8a..c8de23b99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Provide `` and `stripes.hasAnyPermission()`. Refs STCOR-910. * Use the `users-keycloak/_self` endpoint conditionally when the `users-keycloak` interface is present; otherwise, use `bl-users/_self` within `useUserTenantPermissions`. Refs STCOR-905. * Don't override initial discovery and okapi data in test mocks. Refs STCOR-913. +* `` must consume `QueryClient` in order to supply it to `loginServices::logout()`. Refs STCOR-907. ## [10.2.0](https://github.com/folio-org/stripes-core/tree/v10.2.0) (2024-10-11) [Full Changelog](https://github.com/folio-org/stripes-core/compare/v10.1.1...v10.2.0) diff --git a/src/components/Logout/Logout.js b/src/components/Logout/Logout.js index 1f3650ca8..3ba33c401 100644 --- a/src/components/Logout/Logout.js +++ b/src/components/Logout/Logout.js @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useLocation } from 'react-router'; import { FormattedMessage } from 'react-intl'; +import { useQueryClient } from 'react-query'; import { branding } from 'stripes-config'; import { @@ -35,6 +36,7 @@ const Logout = () => { const stripes = useStripes(); const [didLogout, setDidLogout] = useState(false); const location = useLocation(); + const queryClient = useQueryClient(); const messageId = location.pathName === '/logout-timeout' ? 'stripes-core.rtr.idleSession.sessionExpiredSoSad' : 'stripes-core.logoutComplete'; @@ -42,7 +44,7 @@ const Logout = () => { () => { if (stripes.okapi.isAuthenticated) { // returns a promise, which we ignore - logout(stripes.okapi.url, stripes.store) + logout(stripes.okapi.url, stripes.store, queryClient) .then(setDidLogout(true)); } else { setDidLogout(true); diff --git a/src/components/Logout/Logout.test.js b/src/components/Logout/Logout.test.js index f887ee111..ae9ede3b5 100644 --- a/src/components/Logout/Logout.test.js +++ b/src/components/Logout/Logout.test.js @@ -9,6 +9,7 @@ import { getUnauthorizedPathFromSession, logout, setUnauthorizedPathToSession } jest.mock('../OrganizationLogo'); jest.mock('../../StripesContext'); jest.mock('react-router'); +jest.mock('react-query'); jest.mock('../../loginServices', () => ({ ...jest.requireActual('../../loginServices'), diff --git a/src/loginServices.js b/src/loginServices.js index 9216bedb2..132c3f8a0 100644 --- a/src/loginServices.js +++ b/src/loginServices.js @@ -532,7 +532,9 @@ export async function logout(okapiUrl, store, queryClient) { store.dispatch(resetStore()); // clear react-query cache - queryClient.removeQueries(); + if (queryClient) { + queryClient.removeQueries(); + } }) // clear shared storage .then(localforage.removeItem(SESSION_NAME)) diff --git a/src/loginServices.test.js b/src/loginServices.test.js index 16163936c..e05342b5c 100644 --- a/src/loginServices.test.js +++ b/src/loginServices.test.js @@ -564,6 +564,31 @@ describe('logout', () => { expect(global.fetch).not.toHaveBeenCalled(); }); }); + + describe('react-query client', () => { + afterEach(() => { + mockFetchCleanUp(); + }); + + it('calls removeQueries given valid client', async () => { + global.fetch = jest.fn().mockImplementation(() => Promise.resolve()); + const store = { + dispatch: jest.fn(), + }; + const rqc = { + removeQueries: jest.fn(), + }; + + let res; + await logout('', store, rqc) + .then(() => { + res = true; + }); + + expect(res).toBe(true); + expect(rqc.removeQueries).toHaveBeenCalled(); + }); + }); }); describe('getLocale', () => {