wen
This commit is contained in:
parent
1ed7334995
commit
24bf36f431
3 changed files with 227 additions and 0 deletions
99
mnemo_cards_admin/web/src/api/auth.test.ts
Normal file
99
mnemo_cards_admin/web/src/api/auth.test.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
|
import { authApi } from './auth'
|
||||||
|
import { adminApiClient } from './client'
|
||||||
|
|
||||||
|
// Mock the adminApiClient
|
||||||
|
vi.mock('./client', () => ({
|
||||||
|
adminApiClient: {
|
||||||
|
post: vi.fn(),
|
||||||
|
get: vi.fn(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockedAdminApiClient = vi.mocked(adminApiClient)
|
||||||
|
|
||||||
|
describe('authApi', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('requestCode', () => {
|
||||||
|
it('should call adminApiClient.post with correct endpoint', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: { success: true, message: 'Code sent' },
|
||||||
|
}
|
||||||
|
mockedAdminApiClient.post.mockResolvedValue(mockResponse)
|
||||||
|
|
||||||
|
const result = await authApi.requestCode()
|
||||||
|
|
||||||
|
expect(mockedAdminApiClient.post).toHaveBeenCalledWith('/api/v2/admin/auth/request-code')
|
||||||
|
expect(result).toEqual({ success: true, message: 'Code sent' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle API errors', async () => {
|
||||||
|
const error = new Error('Network error')
|
||||||
|
mockedAdminApiClient.post.mockRejectedValue(error)
|
||||||
|
|
||||||
|
await expect(authApi.requestCode()).rejects.toThrow('Network error')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('verifyCode', () => {
|
||||||
|
it('should call adminApiClient.post with code and correct endpoint', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
success: true,
|
||||||
|
token: 'jwt-token',
|
||||||
|
user: { id: 1, name: 'Admin', admin: true },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mockedAdminApiClient.post.mockResolvedValue(mockResponse)
|
||||||
|
|
||||||
|
const result = await authApi.verifyCode('123456')
|
||||||
|
|
||||||
|
expect(mockedAdminApiClient.post).toHaveBeenCalledWith('/api/v2/admin/auth/verify-code', {
|
||||||
|
code: '123456',
|
||||||
|
})
|
||||||
|
expect(result).toEqual(mockResponse.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle invalid code response', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: { success: false, message: 'Invalid code' },
|
||||||
|
}
|
||||||
|
mockedAdminApiClient.post.mockResolvedValue(mockResponse)
|
||||||
|
|
||||||
|
const result = await authApi.verifyCode('invalid')
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: false, message: 'Invalid code' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getCurrentUser', () => {
|
||||||
|
it('should call adminApiClient.get with correct endpoint', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
success: true,
|
||||||
|
user: { id: 1, name: 'Admin', admin: true },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mockedAdminApiClient.get.mockResolvedValue(mockResponse)
|
||||||
|
|
||||||
|
const result = await authApi.getCurrentUser()
|
||||||
|
|
||||||
|
expect(mockedAdminApiClient.get).toHaveBeenCalledWith('/api/v2/admin/auth/me')
|
||||||
|
expect(result).toEqual(mockResponse.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle unauthorized access', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: { success: false, message: 'Not authenticated' },
|
||||||
|
}
|
||||||
|
mockedAdminApiClient.get.mockResolvedValue(mockResponse)
|
||||||
|
|
||||||
|
const result = await authApi.getCurrentUser()
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: false, message: 'Not authenticated' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
127
mnemo_cards_admin/web/src/api/client.test.ts
Normal file
127
mnemo_cards_admin/web/src/api/client.test.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
|
import { apiClient, adminApiClient } from './client'
|
||||||
|
|
||||||
|
describe('API Clients', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
// Clear localStorage
|
||||||
|
localStorage.clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apiClient', () => {
|
||||||
|
it('should be defined and have correct base URL', () => {
|
||||||
|
expect(apiClient).toBeDefined()
|
||||||
|
expect(apiClient.defaults.baseURL).toBe('https://api.mnemo-cards.online')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have interceptors configured', () => {
|
||||||
|
expect(apiClient.interceptors).toBeDefined()
|
||||||
|
expect(apiClient.interceptors.request).toBeDefined()
|
||||||
|
expect(apiClient.interceptors.response).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('adminApiClient', () => {
|
||||||
|
it('should always use production API URL', () => {
|
||||||
|
expect(adminApiClient).toBeDefined()
|
||||||
|
expect(adminApiClient.defaults.baseURL).toBe('https://api.mnemo-cards.online')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be a separate instance from apiClient', () => {
|
||||||
|
expect(adminApiClient).not.toBe(apiClient)
|
||||||
|
expect(adminApiClient.defaults.baseURL).toBe(apiClient.defaults.baseURL)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have interceptors configured', () => {
|
||||||
|
expect(adminApiClient.interceptors).toBeDefined()
|
||||||
|
expect(adminApiClient.interceptors.request).toBeDefined()
|
||||||
|
expect(adminApiClient.interceptors.response).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Auth token handling', () => {
|
||||||
|
it('should handle auth tokens correctly in request interceptor', () => {
|
||||||
|
const token = 'test-admin-token'
|
||||||
|
localStorage.setItem('admin_token', token)
|
||||||
|
|
||||||
|
const requestConfig = { headers: {} }
|
||||||
|
const addAuthToken = (config: any) => {
|
||||||
|
const storedToken = localStorage.getItem('admin_token')
|
||||||
|
if (storedToken) {
|
||||||
|
config.headers.Authorization = `Bearer ${storedToken}`
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = addAuthToken(requestConfig)
|
||||||
|
expect(result.headers.Authorization).toBe(`Bearer ${token}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not add authorization header when no token exists', () => {
|
||||||
|
const requestConfig = { headers: {} }
|
||||||
|
const addAuthToken = (config: any) => {
|
||||||
|
const storedToken = localStorage.getItem('admin_token')
|
||||||
|
if (storedToken) {
|
||||||
|
config.headers.Authorization = `Bearer ${storedToken}`
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = addAuthToken(requestConfig)
|
||||||
|
expect(result.headers.Authorization).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Error handling', () => {
|
||||||
|
it('should handle 401 errors by clearing token and redirecting', async () => {
|
||||||
|
const originalLocation = window.location
|
||||||
|
delete (window as any).location
|
||||||
|
window.location = { href: '' } as any
|
||||||
|
|
||||||
|
const handleAuthError = (error: any) => {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
localStorage.removeItem('admin_token')
|
||||||
|
window.location.href = '/login'
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = { response: { status: 401 } }
|
||||||
|
|
||||||
|
// Set a token first
|
||||||
|
localStorage.setItem('admin_token', 'some-token')
|
||||||
|
|
||||||
|
await expect(handleAuthError(error)).rejects.toEqual(error)
|
||||||
|
|
||||||
|
expect(localStorage.getItem('admin_token')).toBeNull()
|
||||||
|
expect(window.location.href).toBe('/login')
|
||||||
|
|
||||||
|
// Restore original location
|
||||||
|
window.location = originalLocation
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not redirect for non-401 errors', async () => {
|
||||||
|
const originalLocation = window.location
|
||||||
|
delete (window as any).location
|
||||||
|
window.location = { href: '' } as any
|
||||||
|
|
||||||
|
const handleAuthError = (error: any) => {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
localStorage.removeItem('admin_token')
|
||||||
|
window.location.href = '/login'
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = { response: { status: 500 } }
|
||||||
|
|
||||||
|
await expect(handleAuthError(error)).rejects.toEqual(error)
|
||||||
|
|
||||||
|
expect(localStorage.getItem('admin_token')).toBeNull() // Should be cleared from beforeEach
|
||||||
|
expect(window.location.href).toBe('')
|
||||||
|
|
||||||
|
// Restore original location
|
||||||
|
window.location = originalLocation
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
1
mnemo_cards_admin/web/src/test/setup.ts
Normal file
1
mnemo_cards_admin/web/src/test/setup.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
Loading…
Reference in a new issue