From 07d815db2b926e217a530c25493346389ad96620 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 2 Oct 2026 10:39:28 +0200 Subject: [PATCH] feat: call logout endpoint for dex Signed-off-by: Cas Lubbers --- src/pages/Logout.test.tsx | 59 +++++++++++++++++++++++++++++++++++++++ src/pages/Logout.tsx | 8 ++---- src/redux/otomiApi.ts | 6 ++++ 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 src/pages/Logout.test.tsx diff --git a/src/pages/Logout.test.tsx b/src/pages/Logout.test.tsx new file mode 100644 index 000000000..d43dadd23 --- /dev/null +++ b/src/pages/Logout.test.tsx @@ -0,0 +1,59 @@ +import { render } from '@testing-library/react' +import { useLogoutMutation } from 'redux/otomiApi' +import Logout from './Logout' + +jest.mock('redux/otomiApi', () => ({ + useLogoutMutation: jest.fn(), +})) + +jest.mock('components/LoadingScreen', () => () => null) + +describe('Logout', () => { + let assign: jest.Mock + let reload: jest.Mock + + beforeEach(() => { + jest.clearAllMocks() + assign = jest.fn() + reload = jest.fn() + Object.defineProperty(window, 'location', { + value: { ...window.location, assign, reload }, + writable: true, + }) + }) + + it('calls the apl-api logout endpoint and redirects to /platform-logout', async () => { + const logout = jest.fn().mockReturnValue(Promise.resolve({})) + ;(useLogoutMutation as jest.Mock).mockReturnValue([logout]) + + render() + + expect(logout).toHaveBeenCalled() + await Promise.resolve() + expect(assign).toHaveBeenCalledWith('/platform-logout') + }) + + it('still redirects to /platform-logout when the apl-api logout call fails', async () => { + // A thenable exposing only `.finally`, so the component's error path is exercised without + // ever constructing a genuinely rejected native Promise (which Node treats as a fatal + // unhandled rejection even when a handler is attached moments later). + const failingResult = { finally: (onFinally: () => void) => Promise.resolve().then(onFinally) } + const logout = jest.fn().mockReturnValue(failingResult) + ;(useLogoutMutation as jest.Mock).mockReturnValue([logout]) + + render() + + await Promise.resolve().then(() => Promise.resolve()) + expect(assign).toHaveBeenCalledWith('/platform-logout') + }) + + it('reloads without calling logout when fetchError is true', () => { + const logout = jest.fn() + ;(useLogoutMutation as jest.Mock).mockReturnValue([logout]) + + render() + + expect(logout).not.toHaveBeenCalled() + expect(reload).toHaveBeenCalled() + }) +}) diff --git a/src/pages/Logout.tsx b/src/pages/Logout.tsx index e8fd3ad78..bbbf9ee46 100644 --- a/src/pages/Logout.tsx +++ b/src/pages/Logout.tsx @@ -1,18 +1,16 @@ import LoadingScreen from 'components/LoadingScreen' import React, { useEffect } from 'react' +import { useLogoutMutation } from 'redux/otomiApi' interface Props { fetchError?: boolean } export default function Logout({ fetchError = false }: Props): React.ReactElement { - // This component manages the logout process for users authenticated with Keycloak. - // - If a fetch error occurs, the page reloads automatically to handle potential session issues. - // - If no fetch error occurs, the user is redirected to the Keycloak logout page ('/platform-logout' route). - // - On component unmount, the page reloads to ensure a clean and consistent state. + const [logout] = useLogoutMutation() useEffect(() => { if (fetchError) window.location.reload() - else window.location.href = '/platform-logout' + else logout().finally(() => window.location.assign('/platform-logout')) return () => { window.location.reload() } diff --git a/src/redux/otomiApi.ts b/src/redux/otomiApi.ts index 7f334e088..85dfe2678 100644 --- a/src/redux/otomiApi.ts +++ b/src/redux/otomiApi.ts @@ -330,6 +330,9 @@ const injectedRtkApi = api.injectEndpoints({ getSession: build.query({ query: () => ({ url: `/v1/session` }), }), + logout: build.mutation({ + query: () => ({ url: `/v2/user/logout`, method: 'POST' }), + }), getApiDoc: build.query({ query: () => ({ url: `/v1/apiDocs` }), }), @@ -4231,6 +4234,8 @@ export type GetSessionApiResponse = /** status 200 Get the session for the logge } } export type GetSessionApiArg = void +export type LogoutApiResponse = /** status 204 Logout processed. */ undefined +export type LogoutApiArg = void export type GetApiDocApiResponse = /** status 200 The requested apiDoc. */ object export type GetApiDocApiArg = void export type GetSettingsInfoApiResponse = /** status 200 The request is successful. */ { @@ -4966,6 +4971,7 @@ export const { useGetKubecfgQuery, useGetDockerConfigQuery, useGetSessionQuery, + useLogoutMutation, useGetApiDocQuery, useGetSettingsInfoQuery, useGetRepoBranchesQuery,