From fba2efe21e3cdbf557d86302cd2bc552b9fe068a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Fri, 6 Mar 2026 07:43:17 +0800 Subject: [PATCH] Fix MainContainer beforeRouteEnter missing try-catch and next() call Co-Authored-By: Claude Opus 4.6 --- src/views/MainContainer.vue | 17 ++++- tests/views/MainContainerGuard.test.js | 89 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 tests/views/MainContainerGuard.test.js diff --git a/src/views/MainContainer.vue b/src/views/MainContainer.vue index a8ce860..8e1bc98 100644 --- a/src/views/MainContainer.vue +++ b/src/views/MainContainer.vue @@ -23,7 +23,7 @@ import Loading from '@/components/Loading.vue'; import { leaveFilter, leaveConformance } from '@/module/alertModal.js'; import PageAdminStore from '@/stores/pageAdmin.js'; import LoginStore from "@/stores/login.ts"; -import { getCookie } from "@/utils/cookieUtil.js"; +import { getCookie, setCookie } from "@/utils/cookieUtil.js"; import ModalContainer from './AccountManagement/ModalContainer.vue'; export default { @@ -109,8 +109,19 @@ export default { if (!getCookie("isLuciaLoggedIn")) { //這裡不要用pinia的isLoggedIn來檢查,因為會有重新整理時撈不到Persisted value的值的bug if (getCookie('luciaRefreshToken')) { - await loginStore.refreshToken(); - loginStore.setIsLoggedIn(true); + try { + await loginStore.refreshToken(); + loginStore.setIsLoggedIn(true); + setCookie("isLuciaLoggedIn", "true"); + next(); + } catch(error) { + next({ + path: '/login', + query: { + 'return-to': btoa(window.location.href), + } + }); + } } else { next({ path: '/login', diff --git a/tests/views/MainContainerGuard.test.js b/tests/views/MainContainerGuard.test.js new file mode 100644 index 0000000..7ccdb08 --- /dev/null +++ b/tests/views/MainContainerGuard.test.js @@ -0,0 +1,89 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { setActivePinia, createPinia } from 'pinia'; + +// Mock all heavy imports that MainContainer.vue pulls in +vi.mock('@/stores/loading.js', () => ({ + default: () => ({ isLoading: false }), +})); +vi.mock('@/stores/allMapData.js', () => ({ + default: () => ({}), +})); +vi.mock('@/stores/conformance.js', () => ({ + default: () => ({}), +})); +vi.mock('@/stores/pageAdmin.js', () => ({ + default: () => ({}), +})); +vi.mock('@/module/alertModal.js', () => ({ + leaveFilter: vi.fn(), + leaveConformance: vi.fn(), +})); +vi.mock('@/module/apiError.js', () => ({ + default: vi.fn(), +})); +vi.mock('@/router/index.ts', () => ({ + default: { push: vi.fn(), currentRoute: { value: { path: '/' } } }, +})); +vi.mock('@/module/cytoscapeMap.js', () => ({})); + +import LoginStore from '@/stores/login.ts'; +import * as cookieUtil from '@/utils/cookieUtil.js'; + +// Import the component definition to access beforeRouteEnter +import MainContainer from '@/views/MainContainer.vue'; + +describe('MainContainer beforeRouteEnter', () => { + let loginStore; + let next; + + beforeEach(() => { + setActivePinia(createPinia()); + loginStore = LoginStore(); + loginStore.$router = { push: vi.fn() }; + next = vi.fn(); + vi.clearAllMocks(); + // Clear cookies + document.cookie.split(';').forEach((c) => { + const name = c.split('=')[0].trim(); + if (name) { + document.cookie = name + '=; Max-Age=-99999999; path=/'; + } + }); + }); + + const callGuard = () => { + const guard = MainContainer.beforeRouteEnter; + return guard({}, {}, next); + }; + + it('calls next() after successful refreshToken', async () => { + // Not logged in, but has refresh token + document.cookie = 'luciaRefreshToken=some-token'; + vi.spyOn(loginStore, 'refreshToken').mockResolvedValue(); + + await callGuard(); + + expect(loginStore.refreshToken).toHaveBeenCalled(); + expect(next).toHaveBeenCalled(); + }); + + it('redirects to login when refreshToken fails', async () => { + // Not logged in, has refresh token, but refresh fails + document.cookie = 'luciaRefreshToken=some-token'; + vi.spyOn(loginStore, 'refreshToken').mockRejectedValue(new Error('401')); + + await callGuard(); + + expect(next).toHaveBeenCalledWith( + expect.objectContaining({ path: '/login' }), + ); + }); + + it('calls next() when already logged in', async () => { + document.cookie = 'isLuciaLoggedIn=true'; + + await callGuard(); + + expect(next).toHaveBeenCalled(); + }); +});