407 lines
11 KiB
TypeScript
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);
|
|
});
|
|
});
|