feat: Add comprehensive test suite and misc improvements
- Add frontend unit tests with Vitest for components, hooks, pages, and utilities - Add backend tests for webhooks, notifications, middleware, and edge cases - Add ForgotPassword, NotFound, and ResetPassword pages - Add migration for orphaned staff resources conversion - Add coverage directory to gitignore (generated reports) - Various bug fixes and improvements from previous work 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
842
frontend/src/hooks/__tests__/useTicketEmailAddresses.test.ts
Normal file
842
frontend/src/hooks/__tests__/useTicketEmailAddresses.test.ts
Normal file
@@ -0,0 +1,842 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook, waitFor, act } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
|
||||
// Mock the ticket email addresses API module
|
||||
vi.mock('../../api/ticketEmailAddresses', () => ({
|
||||
getTicketEmailAddresses: vi.fn(),
|
||||
getTicketEmailAddress: vi.fn(),
|
||||
createTicketEmailAddress: vi.fn(),
|
||||
updateTicketEmailAddress: vi.fn(),
|
||||
deleteTicketEmailAddress: vi.fn(),
|
||||
testImapConnection: vi.fn(),
|
||||
testSmtpConnection: vi.fn(),
|
||||
fetchEmailsNow: vi.fn(),
|
||||
setAsDefault: vi.fn(),
|
||||
}));
|
||||
|
||||
import {
|
||||
useTicketEmailAddresses,
|
||||
useTicketEmailAddress,
|
||||
useCreateTicketEmailAddress,
|
||||
useUpdateTicketEmailAddress,
|
||||
useDeleteTicketEmailAddress,
|
||||
useTestImapConnection,
|
||||
useTestSmtpConnection,
|
||||
useFetchEmailsNow,
|
||||
useSetAsDefault,
|
||||
} from '../useTicketEmailAddresses';
|
||||
import * as ticketEmailAddressesApi from '../../api/ticketEmailAddresses';
|
||||
|
||||
// Create wrapper with QueryClient
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
return function Wrapper({ children }: { children: React.ReactNode }) {
|
||||
return React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
};
|
||||
};
|
||||
|
||||
describe('useTicketEmailAddresses hooks', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('useTicketEmailAddresses', () => {
|
||||
it('fetches all ticket email addresses', async () => {
|
||||
const mockAddresses = [
|
||||
{
|
||||
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: '#33A1FF',
|
||||
is_active: true,
|
||||
is_default: false,
|
||||
last_check_at: null,
|
||||
emails_processed_count: 0,
|
||||
created_at: '2025-12-02T10:00:00Z',
|
||||
updated_at: '2025-12-02T10:00:00Z',
|
||||
},
|
||||
];
|
||||
vi.mocked(ticketEmailAddressesApi.getTicketEmailAddresses).mockResolvedValue(
|
||||
mockAddresses as any
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useTicketEmailAddresses(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.getTicketEmailAddresses).toHaveBeenCalled();
|
||||
expect(result.current.data).toHaveLength(2);
|
||||
expect(result.current.data?.[0]).toEqual(mockAddresses[0]);
|
||||
expect(result.current.data?.[1]).toEqual(mockAddresses[1]);
|
||||
});
|
||||
|
||||
it('handles empty list', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.getTicketEmailAddresses).mockResolvedValue([]);
|
||||
|
||||
const { result } = renderHook(() => useTicketEmailAddresses(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual([]);
|
||||
});
|
||||
|
||||
it('handles API errors', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.getTicketEmailAddresses).mockRejectedValue(
|
||||
new Error('API Error')
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useTicketEmailAddresses(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useTicketEmailAddress', () => {
|
||||
it('fetches single ticket email address by id', async () => {
|
||||
const mockAddress = {
|
||||
id: 1,
|
||||
tenant: 5,
|
||||
tenant_name: 'Example 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',
|
||||
last_error: null,
|
||||
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(ticketEmailAddressesApi.getTicketEmailAddress).mockResolvedValue(
|
||||
mockAddress as any
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useTicketEmailAddress(1), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.getTicketEmailAddress).toHaveBeenCalledWith(1);
|
||||
expect(result.current.data).toEqual(mockAddress);
|
||||
});
|
||||
|
||||
it('does not fetch when id is 0', async () => {
|
||||
const { result } = renderHook(() => useTicketEmailAddress(0), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.getTicketEmailAddress).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles API errors', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.getTicketEmailAddress).mockRejectedValue(
|
||||
new Error('Not found')
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useTicketEmailAddress(999), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
it('handles addresses with last_error', async () => {
|
||||
const mockAddress = {
|
||||
id: 3,
|
||||
tenant: 5,
|
||||
tenant_name: 'Example Business',
|
||||
display_name: 'Broken Email',
|
||||
email_address: 'broken@example.com',
|
||||
color: '#FF0000',
|
||||
imap_host: 'imap.example.com',
|
||||
imap_port: 993,
|
||||
imap_use_ssl: true,
|
||||
imap_username: 'broken@example.com',
|
||||
imap_folder: 'INBOX',
|
||||
smtp_host: 'smtp.example.com',
|
||||
smtp_port: 587,
|
||||
smtp_use_tls: true,
|
||||
smtp_use_ssl: false,
|
||||
smtp_username: 'broken@example.com',
|
||||
is_active: true,
|
||||
is_default: false,
|
||||
last_check_at: '2025-12-07T10:00:00Z',
|
||||
last_error: 'Authentication failed',
|
||||
emails_processed_count: 0,
|
||||
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(ticketEmailAddressesApi.getTicketEmailAddress).mockResolvedValue(
|
||||
mockAddress as any
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useTicketEmailAddress(3), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data?.last_error).toBe('Authentication failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useCreateTicketEmailAddress', () => {
|
||||
it('creates a new ticket email address', async () => {
|
||||
const newAddress = {
|
||||
display_name: 'Info',
|
||||
email_address: 'info@example.com',
|
||||
color: '#00FF00',
|
||||
imap_host: 'imap.gmail.com',
|
||||
imap_port: 993,
|
||||
imap_use_ssl: true,
|
||||
imap_username: 'info@example.com',
|
||||
imap_password: 'password123',
|
||||
imap_folder: 'INBOX',
|
||||
smtp_host: 'smtp.gmail.com',
|
||||
smtp_port: 587,
|
||||
smtp_use_tls: true,
|
||||
smtp_use_ssl: false,
|
||||
smtp_username: 'info@example.com',
|
||||
smtp_password: 'password123',
|
||||
is_active: true,
|
||||
is_default: false,
|
||||
};
|
||||
const mockResponse = { id: 10, ...newAddress };
|
||||
vi.mocked(ticketEmailAddressesApi.createTicketEmailAddress).mockResolvedValue(
|
||||
mockResponse as any
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useCreateTicketEmailAddress(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(newAddress);
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.createTicketEmailAddress).toHaveBeenCalledWith(newAddress);
|
||||
});
|
||||
|
||||
it('invalidates query cache on success', async () => {
|
||||
const newAddress = {
|
||||
display_name: 'Test',
|
||||
email_address: 'test@example.com',
|
||||
color: '#0000FF',
|
||||
imap_host: 'imap.example.com',
|
||||
imap_port: 993,
|
||||
imap_use_ssl: true,
|
||||
imap_username: 'test@example.com',
|
||||
imap_password: 'pass',
|
||||
imap_folder: 'INBOX',
|
||||
smtp_host: 'smtp.example.com',
|
||||
smtp_port: 587,
|
||||
smtp_use_tls: true,
|
||||
smtp_use_ssl: false,
|
||||
smtp_username: 'test@example.com',
|
||||
smtp_password: 'pass',
|
||||
is_active: true,
|
||||
is_default: false,
|
||||
};
|
||||
const mockResponse = { id: 11, ...newAddress };
|
||||
vi.mocked(ticketEmailAddressesApi.createTicketEmailAddress).mockResolvedValue(
|
||||
mockResponse as any
|
||||
);
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
|
||||
const { result } = renderHook(() => useCreateTicketEmailAddress(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(newAddress);
|
||||
});
|
||||
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['ticketEmailAddresses'] });
|
||||
});
|
||||
|
||||
it('handles creation errors', async () => {
|
||||
const newAddress = {
|
||||
display_name: 'Error',
|
||||
email_address: 'error@example.com',
|
||||
color: '#FF0000',
|
||||
imap_host: 'imap.example.com',
|
||||
imap_port: 993,
|
||||
imap_use_ssl: true,
|
||||
imap_username: 'error@example.com',
|
||||
imap_password: 'pass',
|
||||
imap_folder: 'INBOX',
|
||||
smtp_host: 'smtp.example.com',
|
||||
smtp_port: 587,
|
||||
smtp_use_tls: true,
|
||||
smtp_use_ssl: false,
|
||||
smtp_username: 'error@example.com',
|
||||
smtp_password: 'pass',
|
||||
is_active: true,
|
||||
is_default: false,
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.createTicketEmailAddress).mockRejectedValue(
|
||||
new Error('Validation error')
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useCreateTicketEmailAddress(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
let caughtError;
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.mutateAsync(newAddress);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
});
|
||||
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUpdateTicketEmailAddress', () => {
|
||||
it('updates an existing ticket email address', async () => {
|
||||
const updates = {
|
||||
display_name: 'Updated Support',
|
||||
color: '#FF00FF',
|
||||
};
|
||||
const mockResponse = { id: 1, ...updates };
|
||||
vi.mocked(ticketEmailAddressesApi.updateTicketEmailAddress).mockResolvedValue(
|
||||
mockResponse as any
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useUpdateTicketEmailAddress(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 1, data: updates });
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.updateTicketEmailAddress).toHaveBeenCalledWith(1, updates);
|
||||
});
|
||||
|
||||
it('updates email configuration', async () => {
|
||||
const updates = {
|
||||
imap_host: 'imap.newserver.com',
|
||||
imap_port: 993,
|
||||
imap_password: 'newpassword',
|
||||
};
|
||||
const mockResponse = { id: 2, ...updates };
|
||||
vi.mocked(ticketEmailAddressesApi.updateTicketEmailAddress).mockResolvedValue(
|
||||
mockResponse as any
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useUpdateTicketEmailAddress(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 2, data: updates });
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.updateTicketEmailAddress).toHaveBeenCalledWith(2, updates);
|
||||
});
|
||||
|
||||
it('invalidates queries on success', async () => {
|
||||
const updates = { display_name: 'New Name' };
|
||||
const mockResponse = { id: 3, ...updates };
|
||||
vi.mocked(ticketEmailAddressesApi.updateTicketEmailAddress).mockResolvedValue(
|
||||
mockResponse as any
|
||||
);
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
|
||||
const { result } = renderHook(() => useUpdateTicketEmailAddress(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 3, data: updates });
|
||||
});
|
||||
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['ticketEmailAddresses'] });
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['ticketEmailAddresses', 3] });
|
||||
});
|
||||
|
||||
it('handles update errors', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.updateTicketEmailAddress).mockRejectedValue(
|
||||
new Error('Update failed')
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useUpdateTicketEmailAddress(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
let caughtError;
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.mutateAsync({ id: 999, data: { display_name: 'Test' } });
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
});
|
||||
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useDeleteTicketEmailAddress', () => {
|
||||
it('deletes a ticket email address', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.deleteTicketEmailAddress).mockResolvedValue(undefined);
|
||||
|
||||
const { result } = renderHook(() => useDeleteTicketEmailAddress(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(5);
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.deleteTicketEmailAddress).toHaveBeenCalledWith(5);
|
||||
});
|
||||
|
||||
it('invalidates query cache on success', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.deleteTicketEmailAddress).mockResolvedValue(undefined);
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
|
||||
const { result } = renderHook(() => useDeleteTicketEmailAddress(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(6);
|
||||
});
|
||||
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['ticketEmailAddresses'] });
|
||||
});
|
||||
|
||||
it('handles deletion errors', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.deleteTicketEmailAddress).mockRejectedValue(
|
||||
new Error('Cannot delete default address')
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useDeleteTicketEmailAddress(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
let caughtError;
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.mutateAsync(1);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
});
|
||||
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useTestImapConnection', () => {
|
||||
it('tests IMAP connection successfully', async () => {
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
message: 'IMAP connection successful',
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.testImapConnection).mockResolvedValue(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useTestImapConnection(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.mutateAsync(1);
|
||||
expect(response).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.testImapConnection).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('handles IMAP connection failure', async () => {
|
||||
const mockResponse = {
|
||||
success: false,
|
||||
message: 'Authentication failed',
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.testImapConnection).mockResolvedValue(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useTestImapConnection(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.mutateAsync(2);
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe('Authentication failed');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles API errors during IMAP test', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.testImapConnection).mockRejectedValue(
|
||||
new Error('Network error')
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useTestImapConnection(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
let caughtError;
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.mutateAsync(3);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
});
|
||||
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useTestSmtpConnection', () => {
|
||||
it('tests SMTP connection successfully', async () => {
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
message: 'SMTP connection successful',
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.testSmtpConnection).mockResolvedValue(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useTestSmtpConnection(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.mutateAsync(1);
|
||||
expect(response).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.testSmtpConnection).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('handles SMTP connection failure', async () => {
|
||||
const mockResponse = {
|
||||
success: false,
|
||||
message: 'Could not connect to SMTP server',
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.testSmtpConnection).mockResolvedValue(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useTestSmtpConnection(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.mutateAsync(2);
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe('Could not connect to SMTP server');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles API errors during SMTP test', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.testSmtpConnection).mockRejectedValue(
|
||||
new Error('Timeout')
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useTestSmtpConnection(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
let caughtError;
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.mutateAsync(3);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
});
|
||||
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useFetchEmailsNow', () => {
|
||||
it('fetches emails successfully', async () => {
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
message: 'Successfully fetched 5 new emails',
|
||||
processed: 5,
|
||||
errors: 0,
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.fetchEmailsNow).mockResolvedValue(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useFetchEmailsNow(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.mutateAsync(1);
|
||||
expect(response).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.fetchEmailsNow).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('handles no new emails', async () => {
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
message: 'No new emails',
|
||||
processed: 0,
|
||||
errors: 0,
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.fetchEmailsNow).mockResolvedValue(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useFetchEmailsNow(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.mutateAsync(2);
|
||||
expect(response.processed).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles errors during email fetch', async () => {
|
||||
const mockResponse = {
|
||||
success: false,
|
||||
message: 'Failed to fetch emails',
|
||||
processed: 2,
|
||||
errors: 3,
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.fetchEmailsNow).mockResolvedValue(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useFetchEmailsNow(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.mutateAsync(3);
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.errors).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
it('invalidates queries on success', async () => {
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
message: 'Fetched 3 emails',
|
||||
processed: 3,
|
||||
errors: 0,
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.fetchEmailsNow).mockResolvedValue(mockResponse);
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
|
||||
const { result } = renderHook(() => useFetchEmailsNow(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(4);
|
||||
});
|
||||
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['ticketEmailAddresses'] });
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['tickets'] });
|
||||
});
|
||||
|
||||
it('handles API errors during fetch', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.fetchEmailsNow).mockRejectedValue(
|
||||
new Error('Connection timeout')
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useFetchEmailsNow(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
let caughtError;
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.mutateAsync(5);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
});
|
||||
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSetAsDefault', () => {
|
||||
it('sets email address as default successfully', async () => {
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
message: 'Email address set as default',
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.setAsDefault).mockResolvedValue(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useSetAsDefault(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.mutateAsync(1);
|
||||
expect(response).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
expect(ticketEmailAddressesApi.setAsDefault).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('invalidates query cache on success', async () => {
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
message: 'Email address set as default',
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.setAsDefault).mockResolvedValue(mockResponse);
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
|
||||
const { result } = renderHook(() => useSetAsDefault(), { wrapper });
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(2);
|
||||
});
|
||||
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['ticketEmailAddresses'] });
|
||||
});
|
||||
|
||||
it('handles errors when setting default', async () => {
|
||||
vi.mocked(ticketEmailAddressesApi.setAsDefault).mockRejectedValue(
|
||||
new Error('Cannot set inactive address as default')
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useSetAsDefault(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
let caughtError;
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.mutateAsync(3);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
});
|
||||
|
||||
expect(caughtError).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
it('handles setting already default address', async () => {
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
message: 'Email address is already the default',
|
||||
};
|
||||
vi.mocked(ticketEmailAddressesApi.setAsDefault).mockResolvedValue(mockResponse);
|
||||
|
||||
const { result } = renderHook(() => useSetAsDefault(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.mutateAsync(1);
|
||||
expect(response.message).toBe('Email address is already the default');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user