import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useAuthStore } from '@/stores/auth'; const hostApiFetchMock = vi.hoisted(() => vi.fn()); vi.mock('@/lib/host-api', () => ({ hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args), })); function resetAuthStore() { useAuthStore.setState({ initialized: false, loading: false, error: null, authBase: '', clientId: 'app', accessToken: null, refreshToken: null, tokenType: null, expiresAt: null, user: null, }); } describe('auth store', () => { beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-05-15T08:00:00.000Z')); window.localStorage.clear(); hostApiFetchMock.mockReset(); resetAuthStore(); }); afterEach(() => { vi.useRealTimers(); }); it('starts browser authorization through the host api and stores the session', async () => { hostApiFetchMock.mockResolvedValueOnce({ success: true, token: { access_token: 'access-token', refresh_token: 'refresh-token', token_type: 'Bearer', expires_in: 60, username: 'zhangsan', user_id: '1', tenant_id: 7, dept_id: 9, authorities: ['ROLE_USER'], client_id: 'app', }, }); await useAuthStore.getState().loginWithBrowser(); expect(hostApiFetchMock).toHaveBeenCalledWith('/api/auth/browser/start', { method: 'POST', }); const state = useAuthStore.getState(); expect(state.isAuthenticated()).toBe(true); expect(state.accessToken).toBe('access-token'); expect(state.refreshToken).toBe('refresh-token'); expect(state.expiresAt).toBe(Date.now() + 60_000); expect(state.user).toEqual({ username: 'zhangsan', userId: '1', tenantId: 7, deptId: 9, authorities: ['ROLE_USER'], }); }); it('surfaces browser authorization failures and does not keep a partial session', async () => { hostApiFetchMock.mockResolvedValueOnce({ success: false, error: 'Authorization timed out', }); await expect(useAuthStore.getState().loginWithBrowser()).rejects.toThrow( 'Authorization timed out', ); const state = useAuthStore.getState(); expect(state.loading).toBe(false); expect(state.error).toBe('Authorization timed out'); expect(state.accessToken).toBeNull(); expect(state.isAuthenticated()).toBe(false); }); it('clears a persisted session when the configured SSO gateway changes', () => { useAuthStore.setState({ initialized: false, loading: false, error: null, authBase: 'https://onefeel.brother7.cn/ingress/auth', clientId: 'app', accessToken: 'old-environment-token', refreshToken: 'old-environment-refresh-token', tokenType: 'Bearer', expiresAt: Date.now() + 60_000, user: { username: 'zhangsan', userId: '1', tenantId: 7, deptId: 9, authorities: ['ROLE_USER'], }, }); useAuthStore.getState().init(); const state = useAuthStore.getState(); expect(state.authBase).toBe('https://biz.nianxx.cn/auth/'); expect(state.accessToken).toBeNull(); expect(state.refreshToken).toBeNull(); expect(state.user).toBeNull(); expect(state.isAuthenticated()).toBe(false); }); it('syncs a valid persisted session to Main during init', () => { useAuthStore.setState({ initialized: false, loading: false, error: null, authBase: 'https://biz.nianxx.cn/auth/', clientId: 'app', accessToken: 'persisted-access-token', refreshToken: 'persisted-refresh-token', tokenType: 'Bearer', expiresAt: Date.now() + 60_000, user: { username: 'zhangsan', userId: '1', tenantId: 7, deptId: 9, authorities: ['ROLE_USER'], }, }); useAuthStore.getState().init(); expect(hostApiFetchMock).toHaveBeenCalledWith('/api/auth/session/sync', { method: 'POST', body: JSON.stringify({ accessToken: 'persisted-access-token', refreshToken: 'persisted-refresh-token', tokenType: 'Bearer', expiresAt: Date.now() + 60_000, }), }); }); it('syncs a refreshed session to Main', async () => { hostApiFetchMock .mockResolvedValueOnce({ success: true, token: { access_token: 'new-access-token', refresh_token: 'new-refresh-token', token_type: 'Bearer', expires_in: 120, }, }) .mockResolvedValueOnce({ success: true }); useAuthStore.setState({ initialized: true, loading: false, error: null, authBase: 'https://biz.nianxx.cn/auth/', clientId: 'app', accessToken: 'old-access-token', refreshToken: 'old-refresh-token', tokenType: 'Bearer', expiresAt: Date.now() + 1_000, user: { username: 'zhangsan', userId: '1', tenantId: 7, deptId: 9, authorities: ['ROLE_USER'], }, }); await expect(useAuthStore.getState().refreshSession()).resolves.toBe('new-access-token'); expect(hostApiFetchMock).toHaveBeenNthCalledWith(1, '/api/auth/refresh', { method: 'POST', body: JSON.stringify({ refreshToken: 'old-refresh-token' }), }); expect(hostApiFetchMock).toHaveBeenNthCalledWith(2, '/api/auth/session/sync', { method: 'POST', body: JSON.stringify({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', tokenType: 'Bearer', expiresAt: Date.now() + 120_000, }), }); }); it('logs out remotely and clears the local session', async () => { hostApiFetchMock.mockResolvedValueOnce({ success: true }); useAuthStore.setState({ authBase: 'https://gateway.example.com/auth', clientId: 'app', accessToken: 'access-token', refreshToken: 'refresh-token', tokenType: 'Bearer', expiresAt: Date.now() + 60_000, user: { username: 'zhangsan', userId: '1', tenantId: 7, deptId: 9, authorities: ['ROLE_USER'], }, }); await useAuthStore.getState().logout(); expect(hostApiFetchMock).toHaveBeenCalledWith('/api/auth/logout', { method: 'POST', body: JSON.stringify({ accessToken: 'access-token', }), }); expect(useAuthStore.getState().isAuthenticated()).toBe(false); expect(useAuthStore.getState().accessToken).toBeNull(); expect(useAuthStore.getState().user).toBeNull(); }); });