import { describe, it, expect, vi, beforeEach } from 'vitest'; vi.mock('../client', () => ({ default: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn(), }, })); import { getTicketEmailAddresses, getTicketEmailAddress, createTicketEmailAddress, updateTicketEmailAddress, deleteTicketEmailAddress, testImapConnection, testSmtpConnection, fetchEmailsNow, setAsDefault, TicketEmailAddressListItem, TicketEmailAddress, TicketEmailAddressCreate, TestConnectionResponse, FetchEmailsResponse, } from '../ticketEmailAddresses'; import apiClient from '../client'; describe('ticketEmailAddresses API', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('getTicketEmailAddresses', () => { it('should fetch all ticket email addresses', async () => { const mockAddresses: TicketEmailAddressListItem[] = [ { id: 1, display_name: 'Support', email_address: 'support@example.com', color: '#FF5733', is_active: true, is_default: true, last_check_at: '2025-12-07T10:00:00Z', emails_processed_count: 42, created_at: '2025-12-01T10:00:00Z', updated_at: '2025-12-07T10:00:00Z', }, { id: 2, display_name: 'Sales', email_address: 'sales@example.com', color: '#3357FF', is_active: true, is_default: false, emails_processed_count: 15, created_at: '2025-12-02T10:00:00Z', updated_at: '2025-12-05T10:00:00Z', }, ]; vi.mocked(apiClient.get).mockResolvedValue({ data: mockAddresses }); const result = await getTicketEmailAddresses(); expect(apiClient.get).toHaveBeenCalledWith('/tickets/email-addresses/'); expect(result).toEqual(mockAddresses); expect(result).toHaveLength(2); }); it('should return empty array when no addresses exist', async () => { vi.mocked(apiClient.get).mockResolvedValue({ data: [] }); const result = await getTicketEmailAddresses(); expect(apiClient.get).toHaveBeenCalledWith('/tickets/email-addresses/'); expect(result).toEqual([]); expect(result).toHaveLength(0); }); it('should throw error when API call fails', async () => { const mockError = new Error('Network error'); vi.mocked(apiClient.get).mockRejectedValue(mockError); await expect(getTicketEmailAddresses()).rejects.toThrow('Network error'); expect(apiClient.get).toHaveBeenCalledWith('/tickets/email-addresses/'); }); }); describe('getTicketEmailAddress', () => { it('should fetch a specific ticket email address by ID', async () => { const mockAddress: TicketEmailAddress = { id: 1, tenant: 100, tenant_name: 'Test Business', display_name: 'Support', email_address: 'support@example.com', color: '#FF5733', imap_host: 'imap.gmail.com', imap_port: 993, imap_use_ssl: true, imap_username: 'support@example.com', imap_folder: 'INBOX', smtp_host: 'smtp.gmail.com', smtp_port: 587, smtp_use_tls: true, smtp_use_ssl: false, smtp_username: 'support@example.com', is_active: true, is_default: true, last_check_at: '2025-12-07T10:00:00Z', emails_processed_count: 42, created_at: '2025-12-01T10:00:00Z', updated_at: '2025-12-07T10:00:00Z', is_imap_configured: true, is_smtp_configured: true, is_fully_configured: true, }; vi.mocked(apiClient.get).mockResolvedValue({ data: mockAddress }); const result = await getTicketEmailAddress(1); expect(apiClient.get).toHaveBeenCalledWith('/tickets/email-addresses/1/'); expect(result).toEqual(mockAddress); expect(result.id).toBe(1); expect(result.email_address).toBe('support@example.com'); }); it('should handle fetching with different IDs', async () => { const mockAddress: TicketEmailAddress = { id: 999, tenant: 100, tenant_name: 'Test Business', display_name: 'Sales', email_address: 'sales@example.com', color: '#3357FF', imap_host: 'imap.example.com', imap_port: 993, imap_use_ssl: true, imap_username: 'sales@example.com', imap_folder: 'INBOX', smtp_host: 'smtp.example.com', smtp_port: 587, smtp_use_tls: true, smtp_use_ssl: false, smtp_username: 'sales@example.com', is_active: true, is_default: false, emails_processed_count: 0, created_at: '2025-12-01T10:00:00Z', updated_at: '2025-12-01T10:00:00Z', is_imap_configured: true, is_smtp_configured: true, is_fully_configured: true, }; vi.mocked(apiClient.get).mockResolvedValue({ data: mockAddress }); const result = await getTicketEmailAddress(999); expect(apiClient.get).toHaveBeenCalledWith('/tickets/email-addresses/999/'); expect(result.id).toBe(999); }); it('should throw error when address not found', async () => { const mockError = new Error('Not found'); vi.mocked(apiClient.get).mockRejectedValue(mockError); await expect(getTicketEmailAddress(999)).rejects.toThrow('Not found'); expect(apiClient.get).toHaveBeenCalledWith('/tickets/email-addresses/999/'); }); }); describe('createTicketEmailAddress', () => { it('should create a new ticket email address', async () => { const createData: TicketEmailAddressCreate = { display_name: 'Support', email_address: 'support@example.com', color: '#FF5733', imap_host: 'imap.gmail.com', imap_port: 993, imap_use_ssl: true, imap_username: 'support@example.com', imap_password: 'secure_password', imap_folder: 'INBOX', smtp_host: 'smtp.gmail.com', smtp_port: 587, smtp_use_tls: true, smtp_use_ssl: false, smtp_username: 'support@example.com', smtp_password: 'secure_password', is_active: true, is_default: false, }; const mockResponse: TicketEmailAddress = { id: 1, tenant: 100, tenant_name: 'Test Business', ...createData, imap_password: undefined, // Passwords are not returned in response smtp_password: undefined, last_check_at: undefined, last_error: undefined, emails_processed_count: 0, created_at: '2025-12-07T10:00:00Z', updated_at: '2025-12-07T10:00:00Z', is_imap_configured: true, is_smtp_configured: true, is_fully_configured: true, }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await createTicketEmailAddress(createData); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/', createData); expect(result).toEqual(mockResponse); expect(result.id).toBe(1); expect(result.display_name).toBe('Support'); }); it('should handle creating with minimal required fields', async () => { const createData: TicketEmailAddressCreate = { display_name: 'Minimal', email_address: 'minimal@example.com', color: '#000000', imap_host: 'imap.example.com', imap_port: 993, imap_use_ssl: true, imap_username: 'minimal@example.com', imap_password: 'password', imap_folder: 'INBOX', smtp_host: 'smtp.example.com', smtp_port: 587, smtp_use_tls: false, smtp_use_ssl: false, smtp_username: 'minimal@example.com', smtp_password: 'password', is_active: false, is_default: false, }; const mockResponse: TicketEmailAddress = { id: 2, tenant: 100, tenant_name: 'Test Business', ...createData, imap_password: undefined, smtp_password: undefined, emails_processed_count: 0, created_at: '2025-12-07T10:00:00Z', updated_at: '2025-12-07T10:00:00Z', is_imap_configured: true, is_smtp_configured: true, is_fully_configured: true, }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await createTicketEmailAddress(createData); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/', createData); expect(result.id).toBe(2); }); it('should throw error when validation fails', async () => { const invalidData: TicketEmailAddressCreate = { display_name: '', email_address: 'invalid-email', color: '#FF5733', imap_host: '', imap_port: 993, imap_use_ssl: true, imap_username: '', imap_password: '', imap_folder: 'INBOX', smtp_host: '', smtp_port: 587, smtp_use_tls: true, smtp_use_ssl: false, smtp_username: '', smtp_password: '', is_active: true, is_default: false, }; const mockError = new Error('Validation error'); vi.mocked(apiClient.post).mockRejectedValue(mockError); await expect(createTicketEmailAddress(invalidData)).rejects.toThrow('Validation error'); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/', invalidData); }); }); describe('updateTicketEmailAddress', () => { it('should update an existing ticket email address', async () => { const updateData: Partial = { display_name: 'Updated Support', color: '#00FF00', }; const mockResponse: TicketEmailAddress = { id: 1, tenant: 100, tenant_name: 'Test Business', display_name: 'Updated Support', email_address: 'support@example.com', color: '#00FF00', imap_host: 'imap.gmail.com', imap_port: 993, imap_use_ssl: true, imap_username: 'support@example.com', imap_folder: 'INBOX', smtp_host: 'smtp.gmail.com', smtp_port: 587, smtp_use_tls: true, smtp_use_ssl: false, smtp_username: 'support@example.com', is_active: true, is_default: true, emails_processed_count: 42, created_at: '2025-12-01T10:00:00Z', updated_at: '2025-12-07T11:00:00Z', is_imap_configured: true, is_smtp_configured: true, is_fully_configured: true, }; vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse }); const result = await updateTicketEmailAddress(1, updateData); expect(apiClient.patch).toHaveBeenCalledWith('/tickets/email-addresses/1/', updateData); expect(result).toEqual(mockResponse); expect(result.display_name).toBe('Updated Support'); expect(result.color).toBe('#00FF00'); }); it('should update IMAP configuration', async () => { const updateData: Partial = { imap_host: 'imap.newserver.com', imap_port: 993, imap_password: 'new_password', }; const mockResponse: TicketEmailAddress = { id: 1, tenant: 100, tenant_name: 'Test Business', display_name: 'Support', email_address: 'support@example.com', color: '#FF5733', imap_host: 'imap.newserver.com', imap_port: 993, imap_use_ssl: true, imap_username: 'support@example.com', imap_folder: 'INBOX', smtp_host: 'smtp.gmail.com', smtp_port: 587, smtp_use_tls: true, smtp_use_ssl: false, smtp_username: 'support@example.com', is_active: true, is_default: true, emails_processed_count: 42, created_at: '2025-12-01T10:00:00Z', updated_at: '2025-12-07T11:00:00Z', is_imap_configured: true, is_smtp_configured: true, is_fully_configured: true, }; vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse }); const result = await updateTicketEmailAddress(1, updateData); expect(apiClient.patch).toHaveBeenCalledWith('/tickets/email-addresses/1/', updateData); expect(result.imap_host).toBe('imap.newserver.com'); }); it('should update SMTP configuration', async () => { const updateData: Partial = { smtp_host: 'smtp.newserver.com', smtp_port: 465, smtp_use_tls: false, smtp_use_ssl: true, }; const mockResponse: TicketEmailAddress = { id: 1, tenant: 100, tenant_name: 'Test Business', display_name: 'Support', email_address: 'support@example.com', color: '#FF5733', imap_host: 'imap.gmail.com', imap_port: 993, imap_use_ssl: true, imap_username: 'support@example.com', imap_folder: 'INBOX', smtp_host: 'smtp.newserver.com', smtp_port: 465, smtp_use_tls: false, smtp_use_ssl: true, smtp_username: 'support@example.com', is_active: true, is_default: true, emails_processed_count: 42, created_at: '2025-12-01T10:00:00Z', updated_at: '2025-12-07T11:00:00Z', is_imap_configured: true, is_smtp_configured: true, is_fully_configured: true, }; vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse }); const result = await updateTicketEmailAddress(1, updateData); expect(apiClient.patch).toHaveBeenCalledWith('/tickets/email-addresses/1/', updateData); expect(result.smtp_host).toBe('smtp.newserver.com'); expect(result.smtp_port).toBe(465); expect(result.smtp_use_ssl).toBe(true); }); it('should toggle is_active status', async () => { const updateData: Partial = { is_active: false, }; const mockResponse: TicketEmailAddress = { id: 1, tenant: 100, tenant_name: 'Test Business', display_name: 'Support', email_address: 'support@example.com', color: '#FF5733', imap_host: 'imap.gmail.com', imap_port: 993, imap_use_ssl: true, imap_username: 'support@example.com', imap_folder: 'INBOX', smtp_host: 'smtp.gmail.com', smtp_port: 587, smtp_use_tls: true, smtp_use_ssl: false, smtp_username: 'support@example.com', is_active: false, is_default: true, emails_processed_count: 42, created_at: '2025-12-01T10:00:00Z', updated_at: '2025-12-07T11:00:00Z', is_imap_configured: true, is_smtp_configured: true, is_fully_configured: true, }; vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse }); const result = await updateTicketEmailAddress(1, updateData); expect(apiClient.patch).toHaveBeenCalledWith('/tickets/email-addresses/1/', updateData); expect(result.is_active).toBe(false); }); it('should throw error when update fails', async () => { const updateData: Partial = { display_name: 'Invalid', }; const mockError = new Error('Update failed'); vi.mocked(apiClient.patch).mockRejectedValue(mockError); await expect(updateTicketEmailAddress(1, updateData)).rejects.toThrow('Update failed'); expect(apiClient.patch).toHaveBeenCalledWith('/tickets/email-addresses/1/', updateData); }); }); describe('deleteTicketEmailAddress', () => { it('should delete a ticket email address', async () => { vi.mocked(apiClient.delete).mockResolvedValue({ data: undefined }); await deleteTicketEmailAddress(1); expect(apiClient.delete).toHaveBeenCalledWith('/tickets/email-addresses/1/'); }); it('should handle deletion of different IDs', async () => { vi.mocked(apiClient.delete).mockResolvedValue({ data: undefined }); await deleteTicketEmailAddress(999); expect(apiClient.delete).toHaveBeenCalledWith('/tickets/email-addresses/999/'); }); it('should throw error when deletion fails', async () => { const mockError = new Error('Cannot delete default address'); vi.mocked(apiClient.delete).mockRejectedValue(mockError); await expect(deleteTicketEmailAddress(1)).rejects.toThrow('Cannot delete default address'); expect(apiClient.delete).toHaveBeenCalledWith('/tickets/email-addresses/1/'); }); it('should throw error when address not found', async () => { const mockError = new Error('Not found'); vi.mocked(apiClient.delete).mockRejectedValue(mockError); await expect(deleteTicketEmailAddress(999)).rejects.toThrow('Not found'); expect(apiClient.delete).toHaveBeenCalledWith('/tickets/email-addresses/999/'); }); }); describe('testImapConnection', () => { it('should test IMAP connection successfully', async () => { const mockResponse: TestConnectionResponse = { success: true, message: 'IMAP connection successful', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await testImapConnection(1); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/test_imap/'); expect(result).toEqual(mockResponse); expect(result.success).toBe(true); expect(result.message).toBe('IMAP connection successful'); }); it('should handle failed IMAP connection', async () => { const mockResponse: TestConnectionResponse = { success: false, message: 'Authentication failed: Invalid credentials', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await testImapConnection(1); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/test_imap/'); expect(result.success).toBe(false); expect(result.message).toContain('Invalid credentials'); }); it('should handle network errors during IMAP test', async () => { const mockError = new Error('Network error'); vi.mocked(apiClient.post).mockRejectedValue(mockError); await expect(testImapConnection(1)).rejects.toThrow('Network error'); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/test_imap/'); }); it('should test IMAP connection for different addresses', async () => { const mockResponse: TestConnectionResponse = { success: true, message: 'IMAP connection successful', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); await testImapConnection(42); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/42/test_imap/'); }); }); describe('testSmtpConnection', () => { it('should test SMTP connection successfully', async () => { const mockResponse: TestConnectionResponse = { success: true, message: 'SMTP connection successful', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await testSmtpConnection(1); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/test_smtp/'); expect(result).toEqual(mockResponse); expect(result.success).toBe(true); expect(result.message).toBe('SMTP connection successful'); }); it('should handle failed SMTP connection', async () => { const mockResponse: TestConnectionResponse = { success: false, message: 'Connection refused: Unable to connect to SMTP server', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await testSmtpConnection(1); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/test_smtp/'); expect(result.success).toBe(false); expect(result.message).toContain('Connection refused'); }); it('should handle TLS/SSL errors during SMTP test', async () => { const mockResponse: TestConnectionResponse = { success: false, message: 'SSL certificate verification failed', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await testSmtpConnection(1); expect(result.success).toBe(false); expect(result.message).toContain('SSL certificate'); }); it('should handle network errors during SMTP test', async () => { const mockError = new Error('Network error'); vi.mocked(apiClient.post).mockRejectedValue(mockError); await expect(testSmtpConnection(1)).rejects.toThrow('Network error'); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/test_smtp/'); }); it('should test SMTP connection for different addresses', async () => { const mockResponse: TestConnectionResponse = { success: true, message: 'SMTP connection successful', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); await testSmtpConnection(99); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/99/test_smtp/'); }); }); describe('fetchEmailsNow', () => { it('should fetch emails successfully', async () => { const mockResponse: FetchEmailsResponse = { success: true, message: 'Successfully processed 5 emails', processed: 5, errors: 0, }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await fetchEmailsNow(1); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/fetch_now/'); expect(result).toEqual(mockResponse); expect(result.success).toBe(true); expect(result.processed).toBe(5); expect(result.errors).toBe(0); }); it('should handle fetching with no new emails', async () => { const mockResponse: FetchEmailsResponse = { success: true, message: 'No new emails to process', processed: 0, errors: 0, }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await fetchEmailsNow(1); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/fetch_now/'); expect(result.success).toBe(true); expect(result.processed).toBe(0); }); it('should handle errors during email processing', async () => { const mockResponse: FetchEmailsResponse = { success: false, message: 'Failed to connect to IMAP server', processed: 0, errors: 1, }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await fetchEmailsNow(1); expect(result.success).toBe(false); expect(result.errors).toBe(1); expect(result.message).toContain('Failed to connect'); }); it('should handle partial processing with errors', async () => { const mockResponse: FetchEmailsResponse = { success: true, message: 'Processed 8 emails with 2 errors', processed: 8, errors: 2, }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await fetchEmailsNow(1); expect(result.success).toBe(true); expect(result.processed).toBe(8); expect(result.errors).toBe(2); }); it('should handle network errors during fetch', async () => { const mockError = new Error('Network error'); vi.mocked(apiClient.post).mockRejectedValue(mockError); await expect(fetchEmailsNow(1)).rejects.toThrow('Network error'); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/fetch_now/'); }); it('should fetch emails for different addresses', async () => { const mockResponse: FetchEmailsResponse = { success: true, message: 'Successfully processed 3 emails', processed: 3, errors: 0, }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); await fetchEmailsNow(42); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/42/fetch_now/'); }); }); describe('setAsDefault', () => { it('should set email address as default successfully', async () => { const mockResponse = { success: true, message: 'Email address set as default', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await setAsDefault(2); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/2/set_as_default/'); expect(result).toEqual(mockResponse); expect(result.success).toBe(true); expect(result.message).toBe('Email address set as default'); }); it('should handle setting default for different addresses', async () => { const mockResponse = { success: true, message: 'Email address set as default', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); await setAsDefault(99); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/99/set_as_default/'); }); it('should handle failure to set as default', async () => { const mockResponse = { success: false, message: 'Cannot set inactive email as default', }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse }); const result = await setAsDefault(1); expect(result.success).toBe(false); expect(result.message).toContain('Cannot set inactive'); }); it('should handle network errors when setting default', async () => { const mockError = new Error('Network error'); vi.mocked(apiClient.post).mockRejectedValue(mockError); await expect(setAsDefault(1)).rejects.toThrow('Network error'); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/1/set_as_default/'); }); it('should handle not found errors', async () => { const mockError = new Error('Email address not found'); vi.mocked(apiClient.post).mockRejectedValue(mockError); await expect(setAsDefault(999)).rejects.toThrow('Email address not found'); expect(apiClient.post).toHaveBeenCalledWith('/tickets/email-addresses/999/set_as_default/'); }); }); });