import { describe, it, expect, vi, beforeEach } from 'vitest'; import { AxiosError } from 'axios'; import { login, register, logout, refreshToken, requestPasswordReset, resetPassword, verifyEmail, resendVerificationEmail, type AuthResponse, } from './authService'; import { apiClient } from '@/services/api/client'; // Mock apiClient vi.mock('@/services/api/client', () => ({ apiClient: { post: vi.fn(), get: vi.fn(), }, })); // Mock handleApiServiceError - it always throws, so we let it through // but need the real implementation for error handling tests vi.mock('@/utils/serviceErrorHandler', async () => { const actual = await vi.importActual('@/utils/serviceErrorHandler'); return actual; }); const mockedApiClient = apiClient as { post: ReturnType; get: ReturnType; }; describe('authService', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('login', () => { it('should successfully login and return auth response', async () => { const mockResponse: AuthResponse = { user: { id: '1', email: 'test@example.com', username: 'testuser', }, token: { access_token: 'access-token-123', refresh_token: 'refresh-token-123', expires_in: 3600, }, }; mockedApiClient.post.mockResolvedValue({ data: mockResponse }); const result = await login({ email: 'test@example.com', password: 'password123', }); expect(result).toEqual(mockResponse); expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/login', { email: 'test@example.com', password: 'password123', }); }); it('should throw on login failure', async () => { const mockError = new AxiosError('Login failed'); mockError.response = { status: 401, data: { error: 'Invalid credentials' }, } as any; mockedApiClient.post.mockRejectedValue(mockError); await expect( login({ email: 'test@example.com', password: 'wrongpassword', }), ).rejects.toThrow(); }); it('should throw on network errors', async () => { const mockError = new AxiosError('Network Error'); mockError.request = {}; mockedApiClient.post.mockRejectedValue(mockError); await expect( login({ email: 'test@example.com', password: 'password123', }), ).rejects.toThrow(); }); }); describe('register', () => { it('should successfully register and return auth response', async () => { const mockResponse: AuthResponse = { user: { id: '1', email: 'newuser@example.com', username: 'newuser', }, token: { access_token: 'access-token-123', refresh_token: 'refresh-token-123', expires_in: 3600, }, }; mockedApiClient.post.mockResolvedValue({ data: mockResponse }); const result = await register({ email: 'newuser@example.com', password: 'password123', password_confirm: 'password123', username: 'newuser', }); expect(result).toEqual(mockResponse); expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/register', { email: 'newuser@example.com', password: 'password123', password_confirm: 'password123', username: 'newuser', }); }); it('should throw on registration failure', async () => { const mockError = new AxiosError('Registration failed'); mockError.response = { status: 409, data: { error: 'Email already exists' }, } as any; mockedApiClient.post.mockRejectedValue(mockError); await expect( register({ email: 'existing@example.com', password: 'password123', password_confirm: 'password123', username: 'existinguser', }), ).rejects.toThrow(); }); }); describe('logout', () => { it('should successfully logout', async () => { mockedApiClient.post.mockResolvedValue({ data: {} }); await logout(); expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/logout'); }); it('should throw on logout failure', async () => { const mockError = new AxiosError('Logout failed'); mockError.response = { status: 500, data: { error: 'Internal server error' }, } as any; mockedApiClient.post.mockRejectedValue(mockError); await expect(logout()).rejects.toThrow(); }); }); describe('refreshToken', () => { it('should successfully refresh token and return auth response', async () => { const mockResponse: AuthResponse = { user: { id: '1', email: 'test@example.com', username: 'testuser', }, token: { access_token: 'new-access-token-123', refresh_token: 'new-refresh-token-123', expires_in: 3600, }, }; mockedApiClient.post.mockResolvedValue({ data: mockResponse }); const result = await refreshToken('refresh-token-123'); expect(result).toEqual(mockResponse); expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/refresh', { refreshToken: 'refresh-token-123', }); }); it('should throw on refresh failure', async () => { const mockError = new AxiosError('Refresh failed'); mockError.response = { status: 401, data: { error: 'Invalid refresh token' }, } as any; mockedApiClient.post.mockRejectedValue(mockError); await expect(refreshToken('invalid-token')).rejects.toThrow(); }); }); describe('requestPasswordReset', () => { it('should successfully request password reset', async () => { mockedApiClient.post.mockResolvedValue({ data: {} }); await requestPasswordReset({ email: 'test@example.com', }); expect(mockedApiClient.post).toHaveBeenCalledWith( '/auth/password/reset-request', { email: 'test@example.com', }, ); }); it('should throw on request failure', async () => { const mockError = new AxiosError('Request failed'); mockError.response = { status: 404, data: { error: 'User not found' }, } as any; mockedApiClient.post.mockRejectedValue(mockError); await expect( requestPasswordReset({ email: 'nonexistent@example.com', }), ).rejects.toThrow(); }); }); describe('resetPassword', () => { it('should successfully reset password', async () => { mockedApiClient.post.mockResolvedValue({ data: {} }); await resetPassword({ token: 'reset-token-123', password: 'newpassword123', confirmPassword: 'newpassword123', }); expect(mockedApiClient.post).toHaveBeenCalledWith( '/auth/password/reset', { token: 'reset-token-123', password: 'newpassword123', }, ); }); it('should throw on reset failure', async () => { const mockError = new AxiosError('Reset failed'); mockError.response = { status: 400, data: { error: 'Invalid or expired token' }, } as any; mockedApiClient.post.mockRejectedValue(mockError); await expect( resetPassword({ token: 'invalid-token', password: 'newpassword123', confirmPassword: 'newpassword123', }), ).rejects.toThrow(); }); }); describe('verifyEmail', () => { it('should successfully verify email', async () => { mockedApiClient.get.mockResolvedValue({ data: {} }); await verifyEmail('verification-token-123'); expect(mockedApiClient.get).toHaveBeenCalledWith( '/auth/verify-email?token=verification-token-123', ); }); it('should throw on verification failure', async () => { const mockError = new AxiosError('Verification failed'); mockError.response = { status: 400, data: { error: 'Invalid or expired token' }, } as any; mockedApiClient.get.mockRejectedValue(mockError); await expect(verifyEmail('invalid-token')).rejects.toThrow(); }); }); describe('resendVerificationEmail', () => { it('should successfully resend verification email', async () => { mockedApiClient.post.mockResolvedValue({ data: {} }); await resendVerificationEmail('test@example.com'); expect(mockedApiClient.post).toHaveBeenCalledWith( '/auth/resend-verification', { email: 'test@example.com', }, ); }); it('should throw on resend failure', async () => { const mockError = new AxiosError('Resend failed'); mockError.response = { status: 429, data: { error: 'Too many requests' }, } as any; mockedApiClient.post.mockRejectedValue(mockError); await expect( resendVerificationEmail('test@example.com'), ).rejects.toThrow(); }); }); });