import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { useAuthStore } from '../auth-store'; import { ApiError } from '../api-client'; // Mock auth-api module vi.mock('../auth-api', () => ({ authApi: { login: vi.fn(), register: vi.fn(), logout: vi.fn(), refresh: vi.fn(), exchangeToken: vi.fn(), getProfile: vi.fn(), }, })); // Import mocked module import { authApi } from '../auth-api'; const mockedAuthApi = vi.mocked(authApi); const mockUser = { id: '1', email: 'test@example.com', phone: '0912345678', fullName: 'Nguyen Van A', avatarUrl: null, role: 'user', kycStatus: 'pending', isActive: true, createdAt: '2024-01-01', }; describe('useAuthStore', () => { beforeEach(() => { // Reset store state useAuthStore.setState({ user: null, isAuthenticated: false, isLoading: false, error: null, }); vi.clearAllMocks(); }); afterEach(() => { vi.restoreAllMocks(); }); describe('initial state', () => { it('starts with null user and unauthenticated', () => { const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.isAuthenticated).toBe(false); expect(state.isLoading).toBe(false); expect(state.error).toBeNull(); }); }); describe('login', () => { it('sets isAuthenticated and fetches profile on success', async () => { mockedAuthApi.login.mockResolvedValue({ message: 'ok' }); mockedAuthApi.getProfile.mockResolvedValue(mockUser); await useAuthStore.getState().login({ phone: '0912345678', password: 'pass123' }); const state = useAuthStore.getState(); expect(state.isAuthenticated).toBe(true); expect(state.user).toEqual(mockUser); expect(state.isLoading).toBe(false); expect(state.error).toBeNull(); }); it('sets error on login failure', async () => { mockedAuthApi.login.mockRejectedValue(new ApiError(401, 'Sai mật khẩu')); await expect( useAuthStore.getState().login({ phone: '0912345678', password: 'wrong' }), ).rejects.toThrow(); const state = useAuthStore.getState(); expect(state.isAuthenticated).toBe(false); expect(state.error).toBe('Sai mật khẩu'); expect(state.isLoading).toBe(false); }); it('uses default error message for non-ApiError', async () => { mockedAuthApi.login.mockRejectedValue(new Error('Network error')); await expect( useAuthStore.getState().login({ phone: '0912345678', password: 'pass' }), ).rejects.toThrow(); expect(useAuthStore.getState().error).toBe('Đăng nhập thất bại'); }); }); describe('register', () => { it('sets isAuthenticated and fetches profile on success', async () => { mockedAuthApi.register.mockResolvedValue({ message: 'ok' }); mockedAuthApi.getProfile.mockResolvedValue(mockUser); await useAuthStore.getState().register({ phone: '0912345678', password: 'password123', fullName: 'Nguyen Van A', }); const state = useAuthStore.getState(); expect(state.isAuthenticated).toBe(true); expect(state.user).toEqual(mockUser); }); it('sets error on register failure', async () => { mockedAuthApi.register.mockRejectedValue(new ApiError(409, 'Số điện thoại đã tồn tại')); await expect( useAuthStore.getState().register({ phone: '0912345678', password: 'pass', fullName: 'Test', }), ).rejects.toThrow(); expect(useAuthStore.getState().error).toBe('Số điện thoại đã tồn tại'); }); }); describe('logout', () => { it('clears user and auth state', async () => { useAuthStore.setState({ user: mockUser, isAuthenticated: true }); mockedAuthApi.logout.mockResolvedValue({ message: 'ok' }); await useAuthStore.getState().logout(); const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.isAuthenticated).toBe(false); }); it('clears state even if API logout fails', async () => { useAuthStore.setState({ user: mockUser, isAuthenticated: true }); mockedAuthApi.logout.mockRejectedValue(new Error('Network error')); await useAuthStore.getState().logout(); expect(useAuthStore.getState().user).toBeNull(); expect(useAuthStore.getState().isAuthenticated).toBe(false); }); }); describe('refreshToken', () => { it('returns true and sets authenticated on success', async () => { mockedAuthApi.refresh.mockResolvedValue({ message: 'ok' }); const result = await useAuthStore.getState().refreshToken(); expect(result).toBe(true); expect(useAuthStore.getState().isAuthenticated).toBe(true); }); it('returns false and clears state on failure', async () => { useAuthStore.setState({ user: mockUser, isAuthenticated: true }); mockedAuthApi.refresh.mockRejectedValue(new Error('expired')); const result = await useAuthStore.getState().refreshToken(); expect(result).toBe(false); expect(useAuthStore.getState().user).toBeNull(); expect(useAuthStore.getState().isAuthenticated).toBe(false); }); }); describe('fetchProfile', () => { it('fetches and sets user profile', async () => { mockedAuthApi.getProfile.mockResolvedValue(mockUser); await useAuthStore.getState().fetchProfile(); expect(useAuthStore.getState().user).toEqual(mockUser); expect(useAuthStore.getState().isAuthenticated).toBe(true); }); it('attempts refresh on 401 and retries profile', async () => { mockedAuthApi.getProfile .mockRejectedValueOnce(new ApiError(401, 'Unauthorized')) .mockResolvedValueOnce(mockUser); mockedAuthApi.refresh.mockResolvedValue({ message: 'ok' }); await useAuthStore.getState().fetchProfile(); expect(mockedAuthApi.refresh).toHaveBeenCalled(); expect(useAuthStore.getState().user).toEqual(mockUser); }); }); describe('handleOAuthCallback', () => { it('exchanges token and fetches profile', async () => { mockedAuthApi.exchangeToken.mockResolvedValue({ message: 'ok' }); mockedAuthApi.getProfile.mockResolvedValue(mockUser); await useAuthStore.getState().handleOAuthCallback('access', 'refresh', 3600); expect(mockedAuthApi.exchangeToken).toHaveBeenCalledWith('access', 'refresh', 3600); expect(useAuthStore.getState().isAuthenticated).toBe(true); expect(useAuthStore.getState().user).toEqual(mockUser); }); }); describe('clearError', () => { it('clears the error state', () => { useAuthStore.setState({ error: 'Some error' }); useAuthStore.getState().clearError(); expect(useAuthStore.getState().error).toBeNull(); }); }); });