chore: clean cache
This commit is contained in:
@@ -1,526 +0,0 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { createMockProxyHost } from '../../testUtils/createMockProxyHost';
|
||||
import ProxyHosts from '../ProxyHosts';
|
||||
import * as proxyHostsApi from '../../api/proxyHosts';
|
||||
import * as backupsApi from '../../api/backups';
|
||||
import * as certificatesApi from '../../api/certificates';
|
||||
import * as accessListsApi from '../../api/accessLists';
|
||||
import * as settingsApi from '../../api/settings';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
// Mock toast
|
||||
vi.mock('react-hot-toast', () => ({
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
loading: vi.fn(),
|
||||
dismiss: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock API modules
|
||||
vi.mock('../../api/proxyHosts', () => ({
|
||||
getProxyHosts: vi.fn(),
|
||||
createProxyHost: vi.fn(),
|
||||
updateProxyHost: vi.fn(),
|
||||
deleteProxyHost: vi.fn(),
|
||||
bulkUpdateProxyHostACL: vi.fn(),
|
||||
testProxyHostConnection: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../api/backups', () => ({
|
||||
createBackup: vi.fn(),
|
||||
getBackups: vi.fn(),
|
||||
restoreBackup: vi.fn(),
|
||||
deleteBackup: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../api/certificates', () => ({
|
||||
getCertificates: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../api/accessLists', () => ({
|
||||
accessListsApi: {
|
||||
list: vi.fn(),
|
||||
get: vi.fn(),
|
||||
getTemplates: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
testIP: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../api/settings', () => ({
|
||||
getSettings: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockProxyHosts = [
|
||||
createMockProxyHost({ uuid: 'host-1', name: 'Test Host 1', domain_names: 'test1.example.com', forward_host: '192.168.1.10' }),
|
||||
createMockProxyHost({ uuid: 'host-2', name: 'Test Host 2', domain_names: 'test2.example.com', forward_host: '192.168.1.20' }),
|
||||
createMockProxyHost({ uuid: 'host-3', name: 'Test Host 3', domain_names: 'test3.example.com', forward_host: '192.168.1.30' }),
|
||||
];
|
||||
|
||||
const createQueryClient = () => new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
gcTime: 0,
|
||||
},
|
||||
mutations: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const renderWithProviders = (ui: React.ReactNode) => {
|
||||
const queryClient = createQueryClient();
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>
|
||||
{ui}
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('ProxyHosts - Bulk Delete with Backup', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Setup default mocks
|
||||
vi.mocked(proxyHostsApi.getProxyHosts).mockResolvedValue(mockProxyHosts);
|
||||
vi.mocked(certificatesApi.getCertificates).mockResolvedValue([]);
|
||||
vi.mocked(accessListsApi.accessListsApi.list).mockResolvedValue([]);
|
||||
vi.mocked(settingsApi.getSettings).mockResolvedValue({});
|
||||
vi.mocked(backupsApi.createBackup).mockResolvedValue({
|
||||
filename: 'backup-2024-01-01-12-00-00.db',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders bulk delete button when hosts are selected', async () => {
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts using the select-all checkbox (checkboxes[0])
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Bulk delete button should appear in the selection bar
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manage ACL')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows confirmation modal when delete button is clicked', async () => {
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Wait for bulk action buttons to appear, then click bulk delete button
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manage ACL')).toBeTruthy();
|
||||
});
|
||||
const manageACLButton = screen.getByText('Manage ACL');
|
||||
const deleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement;
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Modal should appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Delete 3 Proxy Hosts?/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
// Should list hosts to be deleted (hosts appear in both table and modal)
|
||||
expect(screen.getAllByText('Test Host 1').length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('Test Host 2').length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('Test Host 3').length).toBeGreaterThan(0);
|
||||
|
||||
// Should mention automatic backup
|
||||
expect(screen.getByText(/automatic backup/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('creates backup before deleting hosts', async () => {
|
||||
vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue();
|
||||
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Wait for bulk action buttons and click delete
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manage ACL')).toBeTruthy();
|
||||
});
|
||||
const manageACLButton = screen.getByText('Manage ACL');
|
||||
const deleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement;
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Wait for modal
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Delete 3 Proxy Hosts?/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
// Click confirm delete
|
||||
const confirmButton = screen.getByText('Delete Permanently');
|
||||
await userEvent.click(confirmButton);
|
||||
|
||||
// Should create backup first
|
||||
await waitFor(() => {
|
||||
expect(backupsApi.createBackup).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Should show loading toast
|
||||
expect(toast.loading).toHaveBeenCalledWith('Creating backup before deletion...');
|
||||
|
||||
// Should show success toast with backup filename
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith('Backup created: backup-2024-01-01-12-00-00.db');
|
||||
});
|
||||
|
||||
// Should then delete the hosts
|
||||
await waitFor(() => {
|
||||
expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('host-1');
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes multiple selected hosts after backup', async () => {
|
||||
vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue();
|
||||
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Wait for bulk action buttons to appear, then click bulk delete button
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manage ACL')).toBeTruthy();
|
||||
});
|
||||
const manageACLButton = screen.getByText('Manage ACL');
|
||||
const deleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement;
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Wait for modal
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Delete 3 Proxy Hosts?/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
// Click confirm delete
|
||||
const confirmButton = screen.getByText('Delete Permanently');
|
||||
await userEvent.click(confirmButton);
|
||||
|
||||
// Should create backup first
|
||||
await waitFor(() => {
|
||||
expect(backupsApi.createBackup).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Should delete all selected hosts
|
||||
await waitFor(() => {
|
||||
expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('host-1');
|
||||
expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('host-2');
|
||||
expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('host-3');
|
||||
});
|
||||
|
||||
// Should show success message
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith(
|
||||
'Successfully deleted 3 host(s). Backup available for restore.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('reports partial success when some deletions fail', async () => {
|
||||
// Make second deletion fail
|
||||
vi.mocked(proxyHostsApi.deleteProxyHost)
|
||||
.mockResolvedValueOnce() // host-1 succeeds
|
||||
.mockRejectedValueOnce(new Error('Network error')) // host-2 fails
|
||||
.mockResolvedValueOnce(); // host-3 succeeds
|
||||
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Wait for bulk action buttons to appear, then click bulk delete button
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manage ACL')).toBeTruthy();
|
||||
});
|
||||
const manageACLButton = screen.getByText('Manage ACL');
|
||||
const deleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement;
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Wait for modal and confirm
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Delete 3 Proxy Hosts?/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
const confirmButton = screen.getByText('Delete Permanently');
|
||||
await userEvent.click(confirmButton);
|
||||
|
||||
// Wait for backup
|
||||
await waitFor(() => {
|
||||
expect(backupsApi.createBackup).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Should show partial success
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith('Deleted 2 host(s), 1 failed');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles backup creation failure', async () => {
|
||||
vi.mocked(backupsApi.createBackup).mockRejectedValue(new Error('Backup failed'));
|
||||
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts using select-all checkbox
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Wait for bulk action buttons to appear, then click bulk delete button
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manage ACL')).toBeTruthy();
|
||||
});
|
||||
const manageACLButton = screen.getByText('Manage ACL');
|
||||
const deleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement;
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Wait for modal and confirm
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Delete 3 Proxy Hosts?/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
const confirmButton = screen.getByText('Delete Permanently');
|
||||
await userEvent.click(confirmButton);
|
||||
|
||||
// Should show error
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith('Backup failed');
|
||||
});
|
||||
|
||||
// Should NOT delete hosts if backup fails
|
||||
expect(proxyHostsApi.deleteProxyHost).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('closes modal after successful deletion', async () => {
|
||||
vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue();
|
||||
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts using select-all checkbox
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Wait for bulk action buttons to appear, then click bulk delete button
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manage ACL')).toBeTruthy();
|
||||
});
|
||||
const manageACLButton = screen.getByText('Manage ACL');
|
||||
const deleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement;
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Wait for modal
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Delete 3 Proxy Hosts?/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
// Click confirm delete
|
||||
const confirmButton = screen.getByText('Delete Permanently');
|
||||
await userEvent.click(confirmButton);
|
||||
|
||||
// Wait for completion
|
||||
await waitFor(() => {
|
||||
expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Modal should close
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/Delete 3 Proxy Hosts?/i)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('clears selection after successful deletion', async () => {
|
||||
vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue();
|
||||
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts using select-all checkbox
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Should show selection count
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/selected/)).toBeTruthy();
|
||||
});
|
||||
|
||||
// Click bulk delete button and confirm (find it via Manage ACL sibling)
|
||||
const manageACLButton = screen.getByText('Manage ACL');
|
||||
const deleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement;
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Delete 3 Proxy Hosts?/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
const confirmButton = screen.getByText('Delete Permanently');
|
||||
await userEvent.click(confirmButton);
|
||||
|
||||
// Wait for completion
|
||||
await waitFor(() => {
|
||||
expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Selection should be cleared - bulk action buttons should disappear
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Manage ACL')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('disables confirm button while creating backup', async () => {
|
||||
// Make backup creation take time
|
||||
vi.mocked(backupsApi.createBackup).mockImplementation(
|
||||
() => new Promise(resolve => setTimeout(() => resolve({ filename: 'backup.db' }), 100))
|
||||
);
|
||||
vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue();
|
||||
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts using select-all checkbox
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Wait for bulk action buttons to appear, then click bulk delete button
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manage ACL')).toBeTruthy();
|
||||
});
|
||||
const manageACLButton = screen.getByText('Manage ACL');
|
||||
const deleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement;
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Wait for modal
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Delete 3 Proxy Hosts?/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
// Click confirm delete
|
||||
const confirmButton = screen.getByText('Delete Permanently');
|
||||
await userEvent.click(confirmButton);
|
||||
|
||||
// Backup should be called (confirms the button works and backup process starts)
|
||||
await waitFor(() => {
|
||||
expect(backupsApi.createBackup).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for deletion to complete to prevent test pollution
|
||||
await waitFor(() => {
|
||||
expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('can cancel deletion from modal', async () => {
|
||||
// Clear mocks to ensure no pollution from previous tests
|
||||
vi.mocked(backupsApi.createBackup).mockClear();
|
||||
vi.mocked(proxyHostsApi.deleteProxyHost).mockClear();
|
||||
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts using select-all checkbox
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]);
|
||||
|
||||
// Wait for bulk action buttons to appear, then click bulk delete button
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manage ACL')).toBeTruthy();
|
||||
});
|
||||
const manageACLButton = screen.getByText('Manage ACL');
|
||||
const deleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement;
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
// Wait for modal
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Delete 3 Proxy Hosts?/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
// Click cancel
|
||||
const cancelButton = screen.getByText('Cancel');
|
||||
await userEvent.click(cancelButton);
|
||||
|
||||
// Modal should close
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/Delete 3 Proxy Hosts?/i)).toBeNull();
|
||||
});
|
||||
|
||||
// Should NOT create backup or delete
|
||||
expect(backupsApi.createBackup).not.toHaveBeenCalled();
|
||||
expect(proxyHostsApi.deleteProxyHost).not.toHaveBeenCalled();
|
||||
|
||||
// Selection should remain
|
||||
expect(screen.getByText(/selected/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows (all) indicator when all hosts selected for deletion', async () => {
|
||||
renderWithProviders(<ProxyHosts />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Host 1')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Select all hosts using the select-all checkbox
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await userEvent.click(checkboxes[0]); // First checkbox is "select all"
|
||||
|
||||
// Should show "(all)" indicator (flexible matcher for spacing)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText((_content, element) => {
|
||||
return element?.textContent === '3 (all) selected';
|
||||
})).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user