- 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>
843 lines
25 KiB
TypeScript
843 lines
25 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
});
|
|
});
|