import { beforeEach, describe, expect, it, vi } from 'vitest'; import { tokenStorage } from '@/api/tokenStorage'; import { authApi } from '@/features/auth/api/authApi'; import { useAuthStore } from '@/features/auth/store/authStore'; vi.mock('@/features/auth/api/authApi', () => ({ authApi: { login: vi.fn(), logout: vi.fn(), refresh: vi.fn(), getMe: vi.fn(), listUsers: vi.fn(), }, })); const mockedAuthApi = vi.mocked(authApi); describe('authStore', () => { beforeEach(() => { tokenStorage.clear(); useAuthStore.setState({ user: null, isInitializing: true }); vi.clearAllMocks(); mockedAuthApi.login.mockResolvedValue({ accessToken: 'access-123', refreshToken: 'refresh-123', user: { id: 'u1', email: 'learner@aplp.io', name: 'Learner' }, }); mockedAuthApi.refresh.mockResolvedValue({ accessToken: 'access-refreshed', refreshToken: 'refresh-refreshed', user: { id: 'u1', email: 'learner@aplp.io', name: 'Learner' }, }); }); it('login stores token and user', async () => { await useAuthStore.getState().login('learner', 'secret'); expect(mockedAuthApi.login).toHaveBeenCalledWith({ username: 'learner', password: 'secret', }); expect(tokenStorage.get()).toBe('access-123'); expect(useAuthStore.getState().user?.email).toBe('learner@aplp.io'); expect(useAuthStore.getState().isInitializing).toBe(false); }); it('login failure leaves user signed out', async () => { mockedAuthApi.login.mockRejectedValue(new Error('invalid credentials')); await expect(useAuthStore.getState().login('learner', 'wrong')).rejects.toThrow( 'invalid credentials', ); expect(tokenStorage.get()).toBeNull(); expect(useAuthStore.getState().user).toBeNull(); }); it('logout clears token and user even when API call fails', async () => { await useAuthStore.getState().login('learner', 'secret'); mockedAuthApi.logout.mockRejectedValue(new Error('network')); await useAuthStore.getState().logout(); expect(tokenStorage.get()).toBeNull(); expect(useAuthStore.getState().user).toBeNull(); }); it('init restores session from refresh token', async () => { tokenStorage.set('old-access', 'refresh-123'); await useAuthStore.getState().init(); expect(mockedAuthApi.refresh).toHaveBeenCalledOnce(); expect(mockedAuthApi.refresh).toHaveBeenCalledWith('refresh-123'); expect(tokenStorage.get()).toBe('access-refreshed'); expect(useAuthStore.getState().user?.id).toBe('u1'); expect(useAuthStore.getState().isInitializing).toBe(false); }); it('init skips refresh when no refresh token is stored', async () => { await useAuthStore.getState().init(); expect(mockedAuthApi.refresh).not.toHaveBeenCalled(); expect(tokenStorage.get()).toBeNull(); expect(useAuthStore.getState().user).toBeNull(); expect(useAuthStore.getState().isInitializing).toBe(false); }); it('init handles expired refresh token gracefully', async () => { tokenStorage.set('old-access', 'refresh-expired'); mockedAuthApi.refresh.mockRejectedValue(new Error('401')); await useAuthStore.getState().init(); expect(tokenStorage.get()).toBeNull(); expect(useAuthStore.getState().user).toBeNull(); expect(useAuthStore.getState().isInitializing).toBe(false); }); });