- Added a test case in CrowdSecConfig to show improved error message when preset is not cached. - Introduced a new test suite for the Dashboard component, verifying counts and health status. - Updated SMTPSettings tests to utilize a shared render function and added tests for backend validation errors. - Modified Security.audit tests to improve input handling and removed redundant export failure test. - Refactored Security tests to remove export functionality and ensure correct rendering of components. - Enhanced UsersPage tests with new scenarios for updating user permissions and manual invite link flow. - Created a new utility for rendering components with a QueryClient and MemoryRouter for better test isolation. - Updated go-test-coverage script to improve error handling and coverage reporting.
300 lines
9.0 KiB
TypeScript
300 lines
9.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { QueryClientProvider } from '@tanstack/react-query';
|
|
import { SecurityNotificationSettingsModal } from '../SecurityNotificationSettingsModal';
|
|
import { createTestQueryClient } from '../../test/createTestQueryClient';
|
|
import * as notificationsApi from '../../api/notifications';
|
|
|
|
// Mock the API
|
|
vi.mock('../../api/notifications', async () => {
|
|
const actual = await vi.importActual('../../api/notifications');
|
|
return {
|
|
...actual,
|
|
getSecurityNotificationSettings: vi.fn(),
|
|
updateSecurityNotificationSettings: vi.fn(),
|
|
};
|
|
});
|
|
|
|
// Mock toast
|
|
vi.mock('../../utils/toast', () => ({
|
|
toast: {
|
|
success: vi.fn(),
|
|
error: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
describe('SecurityNotificationSettingsModal', () => {
|
|
const mockSettings: notificationsApi.SecurityNotificationSettings = {
|
|
enabled: true,
|
|
min_log_level: 'warn',
|
|
notify_waf_blocks: true,
|
|
notify_acl_denials: true,
|
|
notify_rate_limit_hits: false,
|
|
webhook_url: 'https://example.com/webhook',
|
|
email_recipients: 'admin@example.com',
|
|
};
|
|
|
|
let queryClient: ReturnType<typeof createTestQueryClient>;
|
|
|
|
beforeEach(() => {
|
|
queryClient = createTestQueryClient();
|
|
vi.clearAllMocks();
|
|
|
|
vi.mocked(notificationsApi.getSecurityNotificationSettings).mockResolvedValue(mockSettings);
|
|
vi.mocked(notificationsApi.updateSecurityNotificationSettings).mockResolvedValue(mockSettings);
|
|
});
|
|
|
|
const renderModal = (isOpen = true, onClose = vi.fn()) => {
|
|
return render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<SecurityNotificationSettingsModal isOpen={isOpen} onClose={onClose} />
|
|
</QueryClientProvider>
|
|
);
|
|
};
|
|
|
|
it('does not render when isOpen is false', () => {
|
|
renderModal(false);
|
|
expect(screen.queryByText('Security Notification Settings')).toBeFalsy();
|
|
});
|
|
|
|
it('renders the modal when isOpen is true', async () => {
|
|
renderModal();
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Security Notification Settings')).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
it('loads and displays existing settings', async () => {
|
|
renderModal();
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByLabelText('Enable Notifications')).toBeTruthy();
|
|
});
|
|
|
|
// Check that settings are loaded
|
|
const enableSwitch = screen.getByLabelText('Enable Notifications') as HTMLInputElement;
|
|
expect(enableSwitch.checked).toBe(true);
|
|
|
|
const levelSelect = screen.getByLabelText(/minimum log level/i) as HTMLSelectElement;
|
|
expect(levelSelect.value).toBe('warn');
|
|
|
|
const webhookInput = screen.getByPlaceholderText(/your-webhook-endpoint/i) as HTMLInputElement;
|
|
expect(webhookInput.value).toBe('https://example.com/webhook');
|
|
});
|
|
|
|
it('closes modal when close button is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
const mockOnClose = vi.fn();
|
|
renderModal(true, mockOnClose);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Security Notification Settings')).toBeTruthy();
|
|
});
|
|
|
|
const closeButton = screen.getByLabelText('Close');
|
|
await user.click(closeButton);
|
|
|
|
expect(mockOnClose).toHaveBeenCalled();
|
|
});
|
|
|
|
it('closes modal when clicking outside', async () => {
|
|
const user = userEvent.setup();
|
|
const mockOnClose = vi.fn();
|
|
const { container } = renderModal(true, mockOnClose);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Security Notification Settings')).toBeTruthy();
|
|
});
|
|
|
|
// Click on the backdrop
|
|
const backdrop = container.querySelector('.fixed.inset-0');
|
|
if (backdrop) {
|
|
await user.click(backdrop);
|
|
expect(mockOnClose).toHaveBeenCalled();
|
|
}
|
|
});
|
|
|
|
it('submits updated settings', async () => {
|
|
const user = userEvent.setup();
|
|
const mockOnClose = vi.fn();
|
|
renderModal(true, mockOnClose);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByLabelText('Enable Notifications')).toBeTruthy();
|
|
});
|
|
|
|
// Change minimum log level
|
|
const levelSelect = screen.getByLabelText(/minimum log level/i);
|
|
await user.selectOptions(levelSelect, 'error');
|
|
|
|
// Change webhook URL
|
|
const webhookInput = screen.getByPlaceholderText(/your-webhook-endpoint/i);
|
|
await user.clear(webhookInput);
|
|
await user.type(webhookInput, 'https://new-webhook.com');
|
|
|
|
// Submit form
|
|
const saveButton = screen.getByRole('button', { name: /save settings/i });
|
|
await user.click(saveButton);
|
|
|
|
await waitFor(() => {
|
|
expect(notificationsApi.updateSecurityNotificationSettings).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
min_log_level: 'error',
|
|
webhook_url: 'https://new-webhook.com',
|
|
})
|
|
);
|
|
});
|
|
|
|
// Modal should close on success
|
|
await waitFor(() => {
|
|
expect(mockOnClose).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('toggles notification enable/disable', async () => {
|
|
const user = userEvent.setup();
|
|
renderModal();
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByLabelText('Enable Notifications')).toBeTruthy();
|
|
});
|
|
|
|
const enableSwitch = screen.getByLabelText('Enable Notifications') as HTMLInputElement;
|
|
expect(enableSwitch.checked).toBe(true);
|
|
|
|
// Disable notifications
|
|
await user.click(enableSwitch);
|
|
|
|
await waitFor(() => {
|
|
expect(enableSwitch.checked).toBe(false);
|
|
});
|
|
});
|
|
|
|
it('disables controls when notifications are disabled', async () => {
|
|
vi.mocked(notificationsApi.getSecurityNotificationSettings).mockResolvedValue({
|
|
...mockSettings,
|
|
enabled: false,
|
|
});
|
|
|
|
renderModal();
|
|
|
|
// Wait for settings to be loaded and form to render
|
|
await waitFor(() => {
|
|
const enableSwitch = screen.getByLabelText('Enable Notifications') as HTMLInputElement;
|
|
expect(enableSwitch.checked).toBe(false);
|
|
});
|
|
|
|
const levelSelect = screen.getByLabelText(/minimum log level/i) as HTMLSelectElement;
|
|
expect(levelSelect.disabled).toBe(true);
|
|
|
|
const webhookInput = screen.getByPlaceholderText(/your-webhook-endpoint/i) as HTMLInputElement;
|
|
expect(webhookInput.disabled).toBe(true);
|
|
});
|
|
|
|
it('toggles event type filters', async () => {
|
|
const user = userEvent.setup();
|
|
renderModal();
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('WAF Blocks')).toBeTruthy();
|
|
});
|
|
|
|
// Find and toggle WAF blocks switch
|
|
const wafSwitch = screen.getByLabelText('WAF Blocks') as HTMLInputElement;
|
|
expect(wafSwitch.checked).toBe(true);
|
|
|
|
await user.click(wafSwitch);
|
|
|
|
// Submit form
|
|
const saveButton = screen.getByRole('button', { name: /save settings/i });
|
|
await user.click(saveButton);
|
|
|
|
await waitFor(() => {
|
|
expect(notificationsApi.updateSecurityNotificationSettings).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
notify_waf_blocks: false,
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
it('handles API errors gracefully', async () => {
|
|
const user = userEvent.setup();
|
|
const mockOnClose = vi.fn();
|
|
|
|
vi.mocked(notificationsApi.updateSecurityNotificationSettings).mockRejectedValue(
|
|
new Error('API Error')
|
|
);
|
|
|
|
renderModal(true, mockOnClose);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Security Notification Settings')).toBeTruthy();
|
|
});
|
|
|
|
// Submit form
|
|
const saveButton = screen.getByRole('button', { name: /save settings/i });
|
|
await user.click(saveButton);
|
|
|
|
await waitFor(() => {
|
|
expect(notificationsApi.updateSecurityNotificationSettings).toHaveBeenCalled();
|
|
});
|
|
|
|
// Modal should NOT close on error
|
|
expect(mockOnClose).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('shows loading state', () => {
|
|
vi.mocked(notificationsApi.getSecurityNotificationSettings).mockReturnValue(
|
|
new Promise(() => {}) // Never resolves
|
|
);
|
|
|
|
renderModal();
|
|
|
|
expect(screen.getByText('Loading settings...')).toBeTruthy();
|
|
});
|
|
|
|
it('handles email recipients input', async () => {
|
|
const user = userEvent.setup();
|
|
renderModal();
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByPlaceholderText(/admin@example.com/i)).toBeTruthy();
|
|
});
|
|
|
|
const emailInput = screen.getByPlaceholderText(/admin@example.com/i);
|
|
await user.clear(emailInput);
|
|
await user.type(emailInput, 'user1@test.com, user2@test.com');
|
|
|
|
const saveButton = screen.getByRole('button', { name: /save settings/i });
|
|
await user.click(saveButton);
|
|
|
|
await waitFor(() => {
|
|
expect(notificationsApi.updateSecurityNotificationSettings).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
email_recipients: 'user1@test.com, user2@test.com',
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
it('prevents modal content clicks from closing modal', async () => {
|
|
const user = userEvent.setup();
|
|
const mockOnClose = vi.fn();
|
|
renderModal(true, mockOnClose);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Security Notification Settings')).toBeTruthy();
|
|
});
|
|
|
|
// Click inside the modal content
|
|
const modalContent = screen.getByText('Security Notification Settings');
|
|
await user.click(modalContent);
|
|
|
|
// Modal should not close
|
|
expect(mockOnClose).not.toHaveBeenCalled();
|
|
});
|
|
});
|