This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 372287aa fix: redirect to login after logout (#715)
372287aa is described below

commit 372287aa0dcde9cb875e1957998fa380ea79a9a4
Author: Rui <[email protected]>
AuthorDate: Mon Aug 3 11:12:07 2026 +0800

    fix: redirect to login after logout (#715)
---
 web/src/layouts/MainLayout.test.tsx | 99 +++++++++++++++++++++++++++++++++++++
 web/src/layouts/MainLayout.tsx      |  2 +-
 2 files changed, 100 insertions(+), 1 deletion(-)

diff --git a/web/src/layouts/MainLayout.test.tsx 
b/web/src/layouts/MainLayout.test.tsx
new file mode 100644
index 00000000..c0b1bba9
--- /dev/null
+++ b/web/src/layouts/MainLayout.test.tsx
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { logout } from '../api/auth';
+import { LangProvider } from '../i18n/LangContext';
+import useAuthStore from '../stores/authStore';
+import { ThemeProvider } from '../theme/ThemeContext';
+import MainLayout from './MainLayout';
+
+vi.mock('../api/auth', () => ({ logout: vi.fn() }));
+
+vi.mock('antd', async () => {
+  const React = await import('react');
+  const Container = ({ children }: { children?: React.ReactNode }) =>
+    React.createElement(React.Fragment, null, children);
+  const Layout = Object.assign(Container, { Sider: Container, Content: 
Container });
+  type DropdownMenu = {
+    items: Array<{ key?: string; label?: React.ReactNode }>;
+    onClick: (info: { key: string }) => void;
+  };
+
+  return {
+    Layout,
+    Menu: () => null,
+    Breadcrumb: () => null,
+    Avatar: () => React.createElement('span', null, 'avatar'),
+    Dropdown: ({ children, menu }: { children?: React.ReactNode; menu: 
DropdownMenu }) =>
+      React.createElement(
+        'div',
+        null,
+        children,
+        menu.items
+          .map((item) =>
+            item.key
+              ? React.createElement(
+                  'button',
+                  { key: item.key, onClick: () => menu.onClick({ key: 
item.key! }) },
+                  item.label,
+                )
+              : null,
+          ),
+      ),
+    Empty: () => null,
+    Input: () => null,
+    Modal: ({ open, children }: { open?: boolean; children?: React.ReactNode 
}) =>
+      open ? React.createElement('div', null, children) : null,
+    message: { warning: vi.fn() },
+  };
+});
+
+describe('MainLayout authentication navigation', () => {
+  beforeEach(() => {
+    vi.mocked(logout).mockReset().mockResolvedValue(undefined);
+    useAuthStore.getState().login('test-token', 'admin', true);
+  });
+
+  it('replaces a protected route with the login page after logout', async () 
=> {
+    render(
+      <LangProvider>
+        <ThemeProvider>
+          <MemoryRouter initialEntries={['/instance/topic']}>
+            <Routes>
+              <Route path="/login" element={<div>login page</div>} />
+              <Route path="/" element={<MainLayout />}>
+                <Route index element={<div>protected home</div>} />
+                <Route path="instance/topic" element={<div>protected 
topic</div>} />
+              </Route>
+            </Routes>
+          </MemoryRouter>
+        </ThemeProvider>
+      </LangProvider>,
+    );
+
+    expect(screen.getByText('protected topic')).toBeInTheDocument();
+    fireEvent.click(screen.getByRole('button', { name: '退出登录' }));
+
+    await waitFor(() => expect(screen.getByText('login 
page')).toBeInTheDocument());
+    expect(screen.queryByText('protected home')).not.toBeInTheDocument();
+    expect(logout).toHaveBeenCalledOnce();
+    expect(localStorage.getItem('token')).toBeNull();
+  });
+});
diff --git a/web/src/layouts/MainLayout.tsx b/web/src/layouts/MainLayout.tsx
index 4b2907ad..71cbac71 100644
--- a/web/src/layouts/MainLayout.tsx
+++ b/web/src/layouts/MainLayout.tsx
@@ -74,7 +74,7 @@ const MainLayout = () => {
       message.warning('服务端退出失败,已清除本地登录状态');
     } finally {
       clearAuth();
-      navigate('/');
+      navigate('/login', { replace: true });
     }
   };
 

Reply via email to