veza/apps/web/src/features/auth/hooks/useAuth.test.ts
2025-12-12 21:34:34 -05:00

407 lines
11 KiB
TypeScript

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);
});
});