import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock apiClient vi.mock('../client', () => ({ default: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn(), }, })); import { getProfile, updateProfile, uploadAvatar, deleteAvatar, sendVerificationEmail, verifyEmail, requestEmailChange, confirmEmailChange, changePassword, setupTOTP, verifyTOTP, disableTOTP, getRecoveryCodes, regenerateRecoveryCodes, getSessions, revokeSession, revokeOtherSessions, getLoginHistory, sendPhoneVerification, verifyPhoneCode, getUserEmails, addUserEmail, deleteUserEmail, sendUserEmailVerification, verifyUserEmail, setPrimaryEmail, } from '../profile'; import apiClient from '../client'; describe('profile API', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('getProfile', () => { it('fetches user profile', async () => { const mockProfile = { id: 1, email: 'test@example.com', name: 'Test User', email_verified: true, }; vi.mocked(apiClient.get).mockResolvedValue({ data: mockProfile }); const result = await getProfile(); expect(apiClient.get).toHaveBeenCalledWith('/auth/profile/'); expect(result).toEqual(mockProfile); }); }); describe('updateProfile', () => { it('updates profile with provided data', async () => { const mockUpdated = { id: 1, name: 'Updated Name' }; vi.mocked(apiClient.patch).mockResolvedValue({ data: mockUpdated }); const result = await updateProfile({ name: 'Updated Name' }); expect(apiClient.patch).toHaveBeenCalledWith('/auth/profile/', { name: 'Updated Name' }); expect(result).toEqual(mockUpdated); }); }); describe('uploadAvatar', () => { it('uploads avatar file', async () => { const mockResponse = { avatar_url: 'https://example.com/avatar.jpg' }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const file = new File(['test'], 'avatar.jpg', { type: 'image/jpeg' }); const result = await uploadAvatar(file); expect(apiClient.post).toHaveBeenCalledWith( '/auth/profile/avatar/', expect.any(FormData), { headers: { 'Content-Type': 'multipart/form-data' } } ); expect(result.avatar_url).toBe('https://example.com/avatar.jpg'); }); }); describe('deleteAvatar', () => { it('deletes user avatar', async () => { vi.mocked(apiClient.delete).mockResolvedValue({}); await deleteAvatar(); expect(apiClient.delete).toHaveBeenCalledWith('/auth/profile/avatar/'); }); }); describe('email verification', () => { it('sends verification email', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await sendVerificationEmail(); expect(apiClient.post).toHaveBeenCalledWith('/auth/email/verify/send/'); }); it('verifies email with token', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await verifyEmail('verification-token'); expect(apiClient.post).toHaveBeenCalledWith('/auth/email/verify/confirm/', { token: 'verification-token', }); }); }); describe('email change', () => { it('requests email change', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await requestEmailChange('new@example.com'); expect(apiClient.post).toHaveBeenCalledWith('/auth/email/change/', { new_email: 'new@example.com', }); }); it('confirms email change', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await confirmEmailChange('change-token'); expect(apiClient.post).toHaveBeenCalledWith('/auth/email/change/confirm/', { token: 'change-token', }); }); }); describe('changePassword', () => { it('changes password with current and new', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await changePassword('oldPassword', 'newPassword'); expect(apiClient.post).toHaveBeenCalledWith('/auth/password/change/', { current_password: 'oldPassword', new_password: 'newPassword', }); }); }); describe('2FA / TOTP', () => { it('sets up TOTP', async () => { const mockSetup = { secret: 'ABCD1234', qr_code: 'base64...', provisioning_uri: 'otpauth://...', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockSetup }); const result = await setupTOTP(); expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/totp/setup/'); expect(result.secret).toBe('ABCD1234'); }); it('verifies TOTP code', async () => { const mockResponse = { success: true, backup_codes: ['code1', 'code2'] }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await verifyTOTP('123456'); expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/totp/verify/', { code: '123456' }); expect(result.success).toBe(true); expect(result.recovery_codes).toEqual(['code1', 'code2']); }); it('disables TOTP', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await disableTOTP('123456'); expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/disable/', { mfa_code: '123456' }); }); it('gets recovery codes status', async () => { vi.mocked(apiClient.get).mockResolvedValue({ data: {} }); const result = await getRecoveryCodes(); expect(apiClient.get).toHaveBeenCalledWith('/auth/mfa/backup-codes/status/'); expect(result).toEqual([]); }); it('regenerates recovery codes', async () => { const mockCodes = ['code1', 'code2', 'code3']; vi.mocked(apiClient.post).mockResolvedValue({ data: { backup_codes: mockCodes } }); const result = await regenerateRecoveryCodes(); expect(apiClient.post).toHaveBeenCalledWith('/auth/mfa/backup-codes/'); expect(result).toEqual(mockCodes); }); }); describe('sessions', () => { it('gets sessions', async () => { const mockSessions = [ { id: '1', device_info: 'Chrome', ip_address: '1.1.1.1', is_current: true }, ]; vi.mocked(apiClient.get).mockResolvedValue({ data: mockSessions }); const result = await getSessions(); expect(apiClient.get).toHaveBeenCalledWith('/auth/sessions/'); expect(result).toEqual(mockSessions); }); it('revokes session', async () => { vi.mocked(apiClient.delete).mockResolvedValue({}); await revokeSession('session-123'); expect(apiClient.delete).toHaveBeenCalledWith('/auth/sessions/session-123/'); }); it('revokes other sessions', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await revokeOtherSessions(); expect(apiClient.post).toHaveBeenCalledWith('/auth/sessions/revoke-others/'); }); it('gets login history', async () => { const mockHistory = [ { id: '1', timestamp: '2024-01-01', success: true }, ]; vi.mocked(apiClient.get).mockResolvedValue({ data: mockHistory }); const result = await getLoginHistory(); expect(apiClient.get).toHaveBeenCalledWith('/auth/login-history/'); expect(result).toEqual(mockHistory); }); }); describe('phone verification', () => { it('sends phone verification', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await sendPhoneVerification('555-1234'); expect(apiClient.post).toHaveBeenCalledWith('/auth/phone/verify/send/', { phone: '555-1234', }); }); it('verifies phone code', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await verifyPhoneCode('123456'); expect(apiClient.post).toHaveBeenCalledWith('/auth/phone/verify/confirm/', { code: '123456', }); }); }); describe('multiple emails', () => { it('gets user emails', async () => { const mockEmails = [ { id: 1, email: 'primary@example.com', is_primary: true, verified: true }, { id: 2, email: 'secondary@example.com', is_primary: false, verified: false }, ]; vi.mocked(apiClient.get).mockResolvedValue({ data: mockEmails }); const result = await getUserEmails(); expect(apiClient.get).toHaveBeenCalledWith('/auth/emails/'); expect(result).toEqual(mockEmails); }); it('adds user email', async () => { const mockEmail = { id: 3, email: 'new@example.com', is_primary: false, verified: false }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockEmail }); const result = await addUserEmail('new@example.com'); expect(apiClient.post).toHaveBeenCalledWith('/auth/emails/', { email: 'new@example.com' }); expect(result).toEqual(mockEmail); }); it('deletes user email', async () => { vi.mocked(apiClient.delete).mockResolvedValue({}); await deleteUserEmail(2); expect(apiClient.delete).toHaveBeenCalledWith('/auth/emails/2/'); }); it('sends user email verification', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await sendUserEmailVerification(2); expect(apiClient.post).toHaveBeenCalledWith('/auth/emails/2/send-verification/'); }); it('verifies user email', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await verifyUserEmail(2, 'verify-token'); expect(apiClient.post).toHaveBeenCalledWith('/auth/emails/2/verify/', { token: 'verify-token', }); }); it('sets primary email', async () => { vi.mocked(apiClient.post).mockResolvedValue({}); await setPrimaryEmail(2); expect(apiClient.post).toHaveBeenCalledWith('/auth/emails/2/set-primary/'); }); }); });