import { useAuthStore, initAuthStore } from '@/stores/authStore'; import { api, setOnUnauthorizedCallback } from '@/services/api'; import type { User } from '@/types'; // Mock the API service jest.mock('@/services/api', () => ({ api: { isAuthenticated: jest.fn(), getStoredUser: jest.fn(), checkEmail: jest.fn(), requestOTP: jest.fn(), verifyOTP: jest.fn(), logout: jest.fn(), }, setOnUnauthorizedCallback: jest.fn(), })); describe('authStore', () => { beforeEach(() => { // Reset store to initial state before each test useAuthStore.setState({ user: null, isLoading: false, isInitializing: true, isAuthenticated: false, error: null, }); // Clear all mocks jest.clearAllMocks(); }); describe('initialization', () => { it('should initialize with correct default state', () => { const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.isLoading).toBe(false); expect(state.isInitializing).toBe(true); expect(state.isAuthenticated).toBe(false); expect(state.error).toBeNull(); }); it('should setup unauthorized callback on init', () => { initAuthStore(); expect(setOnUnauthorizedCallback).toHaveBeenCalledTimes(1); expect(setOnUnauthorizedCallback).toHaveBeenCalledWith(expect.any(Function)); }); it('should check auth on init when user is authenticated', async () => { const mockUser: User = { user_id: 1, email: 'test@example.com', firstName: 'John', lastName: 'Doe', max_role: 'USER', privileges: '', }; (api.isAuthenticated as jest.Mock).mockResolvedValue(true); (api.getStoredUser as jest.Mock).mockResolvedValue(mockUser); await useAuthStore.getState()._checkAuth(); const state = useAuthStore.getState(); expect(state.user).toEqual(mockUser); expect(state.isAuthenticated).toBe(true); expect(state.isInitializing).toBe(false); expect(state.error).toBeNull(); }); it('should handle unauthenticated state on init', async () => { (api.isAuthenticated as jest.Mock).mockResolvedValue(false); await useAuthStore.getState()._checkAuth(); const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.isAuthenticated).toBe(false); expect(state.isInitializing).toBe(false); }); it('should handle auth check errors', async () => { (api.isAuthenticated as jest.Mock).mockRejectedValue(new Error('Network error')); await useAuthStore.getState()._checkAuth(); const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.isAuthenticated).toBe(false); expect(state.isInitializing).toBe(false); expect(state.error).toEqual({ message: 'Failed to check authentication' }); }); }); describe('checkEmail', () => { it('should return exists: true for existing email', async () => { (api.checkEmail as jest.Mock).mockResolvedValue({ ok: true, data: { exists: true, name: 'John Doe' }, }); const result = await useAuthStore.getState().checkEmail('test@example.com'); expect(result).toEqual({ exists: true, name: 'John Doe' }); expect(api.checkEmail).toHaveBeenCalledWith('test@example.com'); const state = useAuthStore.getState(); expect(state.isLoading).toBe(false); expect(state.error).toBeNull(); }); it('should return exists: false for new email', async () => { (api.checkEmail as jest.Mock).mockResolvedValue({ ok: true, data: { exists: false }, }); const result = await useAuthStore.getState().checkEmail('new@example.com'); expect(result).toEqual({ exists: false }); const state = useAuthStore.getState(); expect(state.isLoading).toBe(false); }); it('should handle API failure gracefully', async () => { (api.checkEmail as jest.Mock).mockResolvedValue({ ok: false, error: { message: 'API error' }, }); const result = await useAuthStore.getState().checkEmail('test@example.com'); expect(result).toEqual({ exists: false }); const state = useAuthStore.getState(); expect(state.isLoading).toBe(false); }); it('should handle network errors', async () => { (api.checkEmail as jest.Mock).mockRejectedValue(new Error('Network error')); const result = await useAuthStore.getState().checkEmail('test@example.com'); expect(result).toEqual({ exists: false }); const state = useAuthStore.getState(); expect(state.error).toEqual({ message: 'Network error. Please check your connection.' }); }); }); describe('requestOtp', () => { it('should successfully request OTP', async () => { (api.requestOTP as jest.Mock).mockResolvedValue({ ok: true, data: { message: 'OTP sent' }, }); const result = await useAuthStore.getState().requestOtp('test@example.com'); expect(result).toEqual({ success: true, skipOtp: false }); expect(api.requestOTP).toHaveBeenCalledWith('test@example.com'); const state = useAuthStore.getState(); expect(state.isLoading).toBe(false); expect(state.error).toBeNull(); }); it('should handle OTP request failure', async () => { (api.requestOTP as jest.Mock).mockResolvedValue({ ok: false, error: { message: 'Failed to send OTP' }, }); const result = await useAuthStore.getState().requestOtp('test@example.com'); expect(result).toEqual({ success: false, skipOtp: false }); const state = useAuthStore.getState(); expect(state.error).toEqual({ message: 'Failed to send verification code. Please try again.' }); }); it('should handle network errors', async () => { (api.requestOTP as jest.Mock).mockRejectedValue(new Error('Network error')); const result = await useAuthStore.getState().requestOtp('test@example.com'); expect(result).toEqual({ success: false, skipOtp: false }); const state = useAuthStore.getState(); expect(state.error).toEqual({ message: 'Network error. Please check your connection.' }); }); }); describe('verifyOtp', () => { it('should successfully verify OTP and authenticate user', async () => { const mockApiResponse = { ok: true, data: { token: 'jwt-token-123', user: { id: 1, email: 'test@example.com', first_name: 'John', last_name: 'Doe', }, }, }; (api.verifyOTP as jest.Mock).mockResolvedValue(mockApiResponse); const result = await useAuthStore.getState().verifyOtp('test@example.com', '123456'); expect(result).toBe(true); expect(api.verifyOTP).toHaveBeenCalledWith('test@example.com', '123456'); const state = useAuthStore.getState(); expect(state.isAuthenticated).toBe(true); expect(state.user).toEqual({ user_id: 1, email: 'test@example.com', firstName: 'John', lastName: 'Doe', max_role: 'USER', privileges: '', }); expect(state.isLoading).toBe(false); expect(state.isInitializing).toBe(false); expect(state.error).toBeNull(); }); it('should handle invalid OTP code', async () => { (api.verifyOTP as jest.Mock).mockResolvedValue({ ok: false, error: { message: 'Invalid OTP' }, }); const result = await useAuthStore.getState().verifyOtp('test@example.com', '000000'); expect(result).toBe(false); const state = useAuthStore.getState(); expect(state.isAuthenticated).toBe(false); expect(state.error).toEqual({ message: 'Invalid verification code. Please try again.' }); }); it('should handle verification errors', async () => { (api.verifyOTP as jest.Mock).mockRejectedValue(new Error('Verification failed')); const result = await useAuthStore.getState().verifyOtp('test@example.com', '123456'); expect(result).toBe(false); const state = useAuthStore.getState(); expect(state.error).toEqual({ message: 'Verification failed' }); }); }); describe('logout', () => { it('should successfully logout and clear state', async () => { // Setup authenticated state useAuthStore.setState({ user: { user_id: 1, email: 'test@example.com', firstName: 'John', lastName: 'Doe', max_role: 'USER', privileges: '', }, isAuthenticated: true, isInitializing: false, }); (api.logout as jest.Mock).mockResolvedValue(undefined); await useAuthStore.getState().logout(); expect(api.logout).toHaveBeenCalledTimes(1); const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.isAuthenticated).toBe(false); expect(state.isLoading).toBe(false); expect(state.isInitializing).toBe(false); expect(state.error).toBeNull(); }); it('should clear state even if API logout fails', async () => { useAuthStore.setState({ user: { user_id: 1, email: 'test@example.com', firstName: 'John', lastName: 'Doe', max_role: 'USER', privileges: '', }, isAuthenticated: true, }); // Mock logout to fail (api.logout as jest.Mock).mockImplementation(() => Promise.reject(new Error('Logout failed'))); // Logout should still clear state even if API call fails // The try/finally in logout ensures state is cleared try { await useAuthStore.getState().logout(); } catch { // Error is expected and caught by try/finally in store } const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.isAuthenticated).toBe(false); }); }); describe('clearError', () => { it('should clear error state', () => { useAuthStore.setState({ error: { message: 'Test error' }, }); useAuthStore.getState().clearError(); const state = useAuthStore.getState(); expect(state.error).toBeNull(); }); }); describe('refreshAuth', () => { it('should re-check authentication', async () => { const mockUser: User = { user_id: 1, email: 'test@example.com', firstName: 'John', lastName: 'Doe', max_role: 'USER', privileges: '', }; (api.isAuthenticated as jest.Mock).mockResolvedValue(true); (api.getStoredUser as jest.Mock).mockResolvedValue(mockUser); await useAuthStore.getState().refreshAuth(); const state = useAuthStore.getState(); expect(state.user).toEqual(mockUser); expect(state.isAuthenticated).toBe(true); }); }); describe('updateUser', () => { it('should update user profile in state', () => { const initialUser: User = { user_id: 1, email: 'test@example.com', firstName: 'John', lastName: 'Doe', max_role: 'USER', privileges: '', }; useAuthStore.setState({ user: initialUser }); useAuthStore.getState().updateUser({ firstName: 'Jane', lastName: 'Smith', }); const state = useAuthStore.getState(); expect(state.user).toEqual({ user_id: 1, email: 'test@example.com', firstName: 'Jane', lastName: 'Smith', max_role: 'USER', privileges: '', }); }); it('should not update if user is null', () => { useAuthStore.setState({ user: null }); useAuthStore.getState().updateUser({ firstName: 'Jane', }); const state = useAuthStore.getState(); expect(state.user).toBeNull(); }); }); describe('unauthorized callback', () => { it('should logout user when unauthorized callback is triggered', async () => { (setOnUnauthorizedCallback as jest.Mock).mockImplementation(() => { // Callback is registered }); (api.logout as jest.Mock).mockResolvedValue(undefined); // Setup authenticated state useAuthStore.setState({ user: { user_id: 1, email: 'test@example.com', firstName: 'John', lastName: 'Doe', max_role: 'USER', privileges: '', }, isAuthenticated: true, }); // Initialize store to set up callback useAuthStore.getState()._setUnauthorizedCallback(); // Verify callback was set expect(setOnUnauthorizedCallback).toHaveBeenCalled(); // Get the callback that was registered const registeredCallback = (setOnUnauthorizedCallback as jest.Mock).mock.calls[0][0]; // Trigger the callback await registeredCallback(); // Wait for async operations to complete await new Promise(resolve => setTimeout(resolve, 50)); const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.isAuthenticated).toBe(false); expect(state.error).toEqual({ message: 'Session expired. Please login again.' }); }); }); describe('selector hooks', () => { it('should provide optimized selectors', () => { const mockUser: User = { user_id: 1, email: 'test@example.com', firstName: 'John', lastName: 'Doe', max_role: 'USER', privileges: '', }; useAuthStore.setState({ user: mockUser, isAuthenticated: true, isLoading: false, error: { message: 'Test error' }, }); // Import at top of file handles this // Just verify the store state is accessible const state = useAuthStore.getState(); expect(state.user).toEqual(mockUser); expect(state.isAuthenticated).toBe(true); expect(state.isLoading).toBe(false); expect(state.error).toEqual({ message: 'Test error' }); }); }); });