254 lines
8.1 KiB
TypeScript
254 lines
8.1 KiB
TypeScript
import React from 'react'
|
|
import { renderHook, act, waitFor } from '@/__tests__/utils/test-helpers'
|
|
import { AuthProvider, AuthContext } from './auth-provider'
|
|
import { useAuth } from '@/hooks/use-auth'
|
|
import { apiClient } from '@/lib/api-client'
|
|
import { mockLoginResponse } from '@/__tests__/fixtures/mock-data'
|
|
|
|
jest.mock('@/lib/api-client')
|
|
jest.mock('next/navigation', () => ({
|
|
useRouter: () => ({
|
|
push: jest.fn(),
|
|
pathname: '/',
|
|
}),
|
|
}))
|
|
|
|
describe('AuthProvider', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
localStorage.clear()
|
|
})
|
|
|
|
describe('initialization', () => {
|
|
it('should initialize with no user when no token exists', async () => {
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false)
|
|
})
|
|
|
|
expect(result.current.user).toBeNull()
|
|
expect(result.current.isAuthenticated).toBe(false)
|
|
})
|
|
|
|
it('should load user from localStorage if token exists', async () => {
|
|
const token = 'test-token'
|
|
localStorage.setItem('auth_token', token)
|
|
|
|
const mockUser = { id: 1, name: 'Test', email: 'test@example.com', created_at: '', updated_at: '' }
|
|
;(apiClient.get as jest.Mock).mockResolvedValueOnce({
|
|
success: true,
|
|
data: mockUser,
|
|
})
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false)
|
|
})
|
|
|
|
expect(result.current.token).toBe(token)
|
|
expect(result.current.user).toEqual(mockUser)
|
|
})
|
|
|
|
it('should clear invalid token and set loading to false', async () => {
|
|
localStorage.setItem('auth_token', 'invalid-token')
|
|
;(apiClient.get as jest.Mock).mockRejectedValueOnce(
|
|
new Error('Unauthorized')
|
|
)
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false)
|
|
})
|
|
|
|
expect(localStorage.getItem('auth_token')).toBeNull()
|
|
expect(result.current.token).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('login', () => {
|
|
it('should successfully login user', async () => {
|
|
const credentials = { email: 'test@example.com', password: 'password123' }
|
|
;(apiClient.post as jest.Mock).mockResolvedValueOnce(mockLoginResponse)
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
await act(async () => {
|
|
await result.current.login(credentials)
|
|
})
|
|
|
|
expect(result.current.token).toBe(mockLoginResponse.data.token)
|
|
expect(result.current.user).toEqual(mockLoginResponse.data.user)
|
|
expect(result.current.isAuthenticated).toBe(true)
|
|
expect(localStorage.getItem('auth_token')).toBe(
|
|
mockLoginResponse.data.token
|
|
)
|
|
})
|
|
|
|
it('should throw error on login failure', async () => {
|
|
const credentials = { email: 'test@example.com', password: 'wrong' }
|
|
const errorMessage = 'Invalid credentials'
|
|
;(apiClient.post as jest.Mock).mockRejectedValueOnce(
|
|
new Error(errorMessage)
|
|
)
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
await expect(
|
|
act(async () => {
|
|
await result.current.login(credentials)
|
|
})
|
|
).rejects.toThrow(errorMessage)
|
|
|
|
expect(result.current.isAuthenticated).toBe(false)
|
|
})
|
|
|
|
it('should handle failed login response', async () => {
|
|
const credentials = { email: 'test@example.com', password: 'password' }
|
|
;(apiClient.post as jest.Mock).mockResolvedValueOnce({
|
|
success: false,
|
|
message: 'Login failed',
|
|
})
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
await expect(
|
|
act(async () => {
|
|
await result.current.login(credentials)
|
|
})
|
|
).rejects.toThrow('Login failed')
|
|
})
|
|
})
|
|
|
|
describe('register', () => {
|
|
it('should successfully register user', async () => {
|
|
const data = {
|
|
name: 'New User',
|
|
email: 'newuser@example.com',
|
|
password: 'password123',
|
|
password_confirmation: 'password123',
|
|
}
|
|
;(apiClient.post as jest.Mock).mockResolvedValueOnce(mockLoginResponse)
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
await act(async () => {
|
|
await result.current.register(data)
|
|
})
|
|
|
|
expect(result.current.token).toBe(mockLoginResponse.data.token)
|
|
expect(result.current.user).toEqual(mockLoginResponse.data.user)
|
|
expect(result.current.isAuthenticated).toBe(true)
|
|
})
|
|
|
|
it('should throw error on registration failure', async () => {
|
|
const data = {
|
|
name: 'New User',
|
|
email: 'newuser@example.com',
|
|
password: 'password123',
|
|
password_confirmation: 'password123',
|
|
}
|
|
const errorMessage = 'Email already exists'
|
|
;(apiClient.post as jest.Mock).mockRejectedValueOnce(
|
|
new Error(errorMessage)
|
|
)
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
await expect(
|
|
act(async () => {
|
|
await result.current.register(data)
|
|
})
|
|
).rejects.toThrow(errorMessage)
|
|
|
|
expect(result.current.isAuthenticated).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('logout', () => {
|
|
it('should clear user and token on logout', async () => {
|
|
const token = 'test-token'
|
|
localStorage.setItem('auth_token', token)
|
|
|
|
const mockUser = { id: 1, name: 'Test', email: 'test@example.com', created_at: '', updated_at: '' }
|
|
;(apiClient.get as jest.Mock).mockResolvedValueOnce({
|
|
success: true,
|
|
data: mockUser,
|
|
})
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false)
|
|
})
|
|
|
|
act(() => {
|
|
result.current.logout()
|
|
})
|
|
|
|
expect(result.current.token).toBeNull()
|
|
expect(result.current.user).toBeNull()
|
|
expect(result.current.isAuthenticated).toBe(false)
|
|
expect(localStorage.getItem('auth_token')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('authentication state', () => {
|
|
it('should correctly report isAuthenticated status', async () => {
|
|
// Set token BEFORE rendering hook so AuthProvider sees it on mount
|
|
localStorage.setItem('auth_token', 'test-token')
|
|
const mockUser = { id: 1, name: 'Test', email: 'test@example.com', created_at: '', updated_at: '' }
|
|
;(apiClient.get as jest.Mock).mockResolvedValueOnce({
|
|
success: true,
|
|
data: mockUser,
|
|
})
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AuthProvider>{children}</AuthProvider>
|
|
)
|
|
const { result } = renderHook(() => useAuth(), { wrapper })
|
|
|
|
// Should be loading initially
|
|
expect(result.current.isLoading).toBe(true)
|
|
|
|
// After loading completes
|
|
await waitFor(() => {
|
|
expect(result.current.isLoading).toBe(false)
|
|
})
|
|
|
|
// Token and authentication should be set
|
|
expect(result.current.token).toBe('test-token')
|
|
expect(result.current.isAuthenticated).toBe(true)
|
|
})
|
|
})
|
|
})
|