import { describe, it, expect, beforeEach, vi } from 'vitest'; import { renderHook, act, waitFor } from '@testing-library/react'; import { useAuthStore } from './useAuth'; import { useLogin } from './useLogin'; import { useRegister } from './useRegister'; import { useLogout } from './useLogout'; import { usePasswordReset } from './usePasswordReset'; import * as authService from '../services/authService'; // Mock react-router-dom const mockNavigate = vi.fn(); vi.mock('react-router-dom', async () => { const actual = await vi.importActual('react-router-dom'); return { ...actual, useNavigate: () => mockNavigate, }; }); // Mock authService vi.mock('../services/authService', () => ({ login: vi.fn(), register: vi.fn(), logout: vi.fn(), requestPasswordReset: vi.fn(), resetPassword: vi.fn(), })); describe('useAuthStore', () => { beforeEach(() => { // Reset store state useAuthStore.setState({ user: null, accessToken: null, refreshToken: null, isAuthenticated: false, }); }); it('should have initial state', () => { const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.accessToken).toBeNull(); expect(state.refreshToken).toBeNull(); expect(state.isAuthenticated).toBe(false); }); it('should set auth state', () => { const user = { id: 1, email: 'test@example.com', username: 'testuser' }; const accessToken = 'access-token-123'; const refreshToken = 'refresh-token-123'; act(() => { useAuthStore.getState().setAuth(user, accessToken, refreshToken); }); const state = useAuthStore.getState(); expect(state.user).toEqual(user); expect(state.accessToken).toBe(accessToken); expect(state.refreshToken).toBe(refreshToken); expect(state.isAuthenticated).toBe(true); }); it('should clear auth state', () => { const user = { id: 1, email: 'test@example.com', username: 'testuser' }; act(() => { useAuthStore.getState().setAuth(user, 'token', 'refresh'); }); act(() => { useAuthStore.getState().clearAuth(); }); const state = useAuthStore.getState(); expect(state.user).toBeNull(); expect(state.accessToken).toBeNull(); expect(state.refreshToken).toBeNull(); expect(state.isAuthenticated).toBe(false); }); it('should update tokens', () => { const user = { id: 1, email: 'test@example.com', username: 'testuser' }; act(() => { useAuthStore.getState().setAuth(user, 'old-token', 'old-refresh'); }); act(() => { useAuthStore.getState().updateTokens('new-token', 'new-refresh'); }); const state = useAuthStore.getState(); expect(state.accessToken).toBe('new-token'); expect(state.refreshToken).toBe('new-refresh'); expect(state.user).toEqual(user); // User should remain unchanged }); }); describe('useLogin', () => { beforeEach(() => { vi.clearAllMocks(); useAuthStore.setState({ user: null, accessToken: null, refreshToken: null, isAuthenticated: false, }); }); it('should login successfully', async () => { const mockResponse = { user: { id: 1, email: 'test@example.com', username: 'testuser' }, accessToken: 'access-token-123', refreshToken: 'refresh-token-123', }; vi.mocked(authService.login).mockResolvedValue(mockResponse); const { result } = renderHook(() => useLogin()); await act(async () => { await result.current.handleLogin({ email: 'test@example.com', password: 'password123', }); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(authService.login).toHaveBeenCalledWith({ email: 'test@example.com', password: 'password123', }); expect(useAuthStore.getState().user).toEqual(mockResponse.user); expect(useAuthStore.getState().accessToken).toBe(mockResponse.accessToken); expect(mockNavigate).toHaveBeenCalledWith('/dashboard'); expect(result.current.error).toBeNull(); }); it('should handle login error', async () => { const mockError = new Error('Invalid credentials'); vi.mocked(authService.login).mockRejectedValue(mockError); const { result } = renderHook(() => useLogin()); await act(async () => { await result.current.handleLogin({ email: 'test@example.com', password: 'wrongpassword', }); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(result.current.error).toEqual(mockError); expect(useAuthStore.getState().isAuthenticated).toBe(false); }); it('should set loading state during login', async () => { vi.mocked(authService.login).mockImplementation( () => new Promise((resolve) => { setTimeout(() => { resolve({ user: { id: 1, email: 'test@example.com', username: 'testuser' }, accessToken: 'token', refreshToken: 'refresh', }); }, 100); }), ); const { result } = renderHook(() => useLogin()); act(() => { result.current.handleLogin({ email: 'test@example.com', password: 'password123', }); }); expect(result.current.loading).toBe(true); await waitFor(() => { expect(result.current.loading).toBe(false); }); }); }); describe('useRegister', () => { beforeEach(() => { vi.clearAllMocks(); useAuthStore.setState({ user: null, accessToken: null, refreshToken: null, isAuthenticated: false, }); }); it('should register successfully', async () => { const mockResponse = { user: { id: 1, email: 'new@example.com', username: 'newuser' }, accessToken: 'access-token-123', refreshToken: 'refresh-token-123', }; vi.mocked(authService.register).mockResolvedValue(mockResponse); const { result } = renderHook(() => useRegister()); await act(async () => { await result.current.handleRegister({ email: 'new@example.com', password: 'password123', confirmPassword: 'password123', username: 'newuser', }); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(authService.register).toHaveBeenCalledWith({ email: 'new@example.com', password: 'password123', confirmPassword: 'password123', username: 'newuser', }); expect(useAuthStore.getState().user).toEqual(mockResponse.user); expect(mockNavigate).toHaveBeenCalledWith('/dashboard'); expect(result.current.error).toBeNull(); }); it('should handle registration error', async () => { const mockError = new Error('Email already exists'); vi.mocked(authService.register).mockRejectedValue(mockError); const { result } = renderHook(() => useRegister()); await act(async () => { await result.current.handleRegister({ email: 'existing@example.com', password: 'password123', confirmPassword: 'password123', username: 'existinguser', }); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(result.current.error).toEqual(mockError); }); }); describe('useLogout', () => { beforeEach(() => { vi.clearAllMocks(); const user = { id: 1, email: 'test@example.com', username: 'testuser' }; useAuthStore.setState({ user, accessToken: 'token', refreshToken: 'refresh', isAuthenticated: true, }); }); it('should logout successfully', async () => { vi.mocked(authService.logout).mockResolvedValue(undefined); const { result } = renderHook(() => useLogout()); await act(async () => { await result.current.handleLogout(); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(authService.logout).toHaveBeenCalled(); expect(useAuthStore.getState().isAuthenticated).toBe(false); expect(useAuthStore.getState().user).toBeNull(); expect(mockNavigate).toHaveBeenCalledWith('/login'); expect(result.current.error).toBeNull(); }); it('should clear auth even on logout error', async () => { const mockError = new Error('Logout failed'); vi.mocked(authService.logout).mockRejectedValue(mockError); const { result } = renderHook(() => useLogout()); await act(async () => { await result.current.handleLogout(); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(useAuthStore.getState().isAuthenticated).toBe(false); expect(mockNavigate).toHaveBeenCalledWith('/login'); }); }); describe('usePasswordReset', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should request password reset successfully', async () => { vi.mocked(authService.requestPasswordReset).mockResolvedValue(undefined); const { result } = renderHook(() => usePasswordReset()); await act(async () => { await result.current.handleRequestReset({ email: 'test@example.com', }); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(authService.requestPasswordReset).toHaveBeenCalledWith({ email: 'test@example.com', }); expect(result.current.success).toBe(true); expect(result.current.error).toBeNull(); }); it('should handle password reset request error', async () => { const mockError = new Error('User not found'); vi.mocked(authService.requestPasswordReset).mockRejectedValue(mockError); const { result } = renderHook(() => usePasswordReset()); await act(async () => { await result.current.handleRequestReset({ email: 'nonexistent@example.com', }); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(result.current.error).toEqual(mockError); expect(result.current.success).toBe(false); }); it('should reset password successfully', async () => { vi.mocked(authService.resetPassword).mockResolvedValue(undefined); const { result } = renderHook(() => usePasswordReset()); await act(async () => { await result.current.handleReset({ token: 'reset-token-123', password: 'newpassword123', confirmPassword: 'newpassword123', }); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(authService.resetPassword).toHaveBeenCalledWith({ token: 'reset-token-123', password: 'newpassword123', confirmPassword: 'newpassword123', }); expect(result.current.success).toBe(true); expect(result.current.error).toBeNull(); }); it('should handle password reset error', async () => { const mockError = new Error('Invalid token'); vi.mocked(authService.resetPassword).mockRejectedValue(mockError); const { result } = renderHook(() => usePasswordReset()); await act(async () => { await result.current.handleReset({ token: 'invalid-token', password: 'newpassword123', confirmPassword: 'newpassword123', }); }); await waitFor(() => { expect(result.current.loading).toBe(false); }); expect(result.current.error).toEqual(mockError); expect(result.current.success).toBe(false); }); });