cb1982c39e
- Created useAuthApi composable for JWT token lifecycle management - Implemented setupAuthInterceptor for automatic Authorization header injection - Added LoginPage.vue with username/password form - Configured router to redirect to /login for unauthenticated access - Token stored in localStorage with expiration tracking - Automatic token validation and cleanup on expiration - All fetch requests automatically include Bearer token - Unit tests for login, logout, token validation flows This enables frontend to authenticate via JWT tokens in production mode. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
86 lines
2.6 KiB
TypeScript
86 lines
2.6 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
import { useAuthApi } from '../useAuthApi'
|
|
|
|
describe('useAuthApi', () => {
|
|
beforeEach(() => {
|
|
// Clear localStorage
|
|
localStorage.clear()
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('should initialize with no authentication', () => {
|
|
const { authState } = useAuthApi()
|
|
expect(authState.value.isAuthenticated).toBe(false)
|
|
expect(authState.value.token).toBeNull()
|
|
})
|
|
|
|
it('should login successfully', async () => {
|
|
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({
|
|
accessToken: 'test-token',
|
|
expiresIn: 3600,
|
|
tokenType: 'Bearer',
|
|
}),
|
|
})
|
|
|
|
const { login, authState } = useAuthApi()
|
|
const result = await login('testuser', 'password', 'Admin')
|
|
|
|
expect(result).toBe(true)
|
|
expect(authState.value.token).toBe('test-token')
|
|
expect(authState.value.isAuthenticated).toBe(true)
|
|
expect(localStorage.getItem('kartsell_auth_token')).toBe('test-token')
|
|
})
|
|
|
|
it('should handle login failure', async () => {
|
|
global.fetch = vi.fn().mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 401,
|
|
json: async () => ({ message: 'Invalid credentials' }),
|
|
})
|
|
|
|
const { login, authState, error } = useAuthApi()
|
|
const result = await login('testuser', 'wrongpassword', 'Admin')
|
|
|
|
expect(result).toBe(false)
|
|
expect(authState.value.isAuthenticated).toBe(false)
|
|
expect(error.value).toBeTruthy()
|
|
})
|
|
|
|
it('should logout successfully', () => {
|
|
localStorage.setItem('kartsell_auth_token', 'test-token')
|
|
localStorage.setItem('kartsell_expires_at', (Date.now() + 3600000).toString())
|
|
|
|
const { logout, authState } = useAuthApi()
|
|
logout()
|
|
|
|
expect(authState.value.token).toBeNull()
|
|
expect(authState.value.isAuthenticated).toBe(false)
|
|
expect(localStorage.getItem('kartsell_auth_token')).toBeNull()
|
|
})
|
|
|
|
it('should get token if valid', () => {
|
|
const expiresAt = Date.now() + 3600000 // 1 hour from now
|
|
localStorage.setItem('kartsell_auth_token', 'test-token')
|
|
localStorage.setItem('kartsell_expires_at', expiresAt.toString())
|
|
|
|
const { getToken } = useAuthApi()
|
|
const token = getToken()
|
|
|
|
expect(token).toBe('test-token')
|
|
})
|
|
|
|
it('should clear token if expired', () => {
|
|
const expiresAt = Date.now() - 3600000 // 1 hour ago
|
|
localStorage.setItem('kartsell_auth_token', 'test-token')
|
|
localStorage.setItem('kartsell_expires_at', expiresAt.toString())
|
|
|
|
const { getToken, authState } = useAuthApi()
|
|
const token = getToken()
|
|
|
|
expect(token).toBeNull()
|
|
expect(authState.value.isAuthenticated).toBe(false)
|
|
})
|
|
})
|