feat(tests): enhance test coverage and error handling across various components

- 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.
This commit is contained in:
GitHub Actions
2025-12-11 00:26:07 +00:00
parent ca4cfc4e65
commit e299aa6b52
81 changed files with 8960 additions and 450 deletions

View File

@@ -0,0 +1,251 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactNode } from 'react';
import {
useSecurityNotificationSettings,
useUpdateSecurityNotificationSettings,
} from '../useNotifications';
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('useNotifications hooks', () => {
let queryClient: QueryClient;
const createWrapper = () => {
return ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
beforeEach(() => {
queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
vi.clearAllMocks();
});
describe('useSecurityNotificationSettings', () => {
it('fetches security notification settings', async () => {
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',
};
vi.mocked(notificationsApi.getSecurityNotificationSettings).mockResolvedValue(mockSettings);
const { result } = renderHook(() => useSecurityNotificationSettings(), {
wrapper: createWrapper(),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockSettings);
expect(notificationsApi.getSecurityNotificationSettings).toHaveBeenCalledTimes(1);
});
it('handles fetch errors', async () => {
vi.mocked(notificationsApi.getSecurityNotificationSettings).mockRejectedValue(
new Error('Network error')
);
const { result } = renderHook(() => useSecurityNotificationSettings(), {
wrapper: createWrapper(),
});
await waitFor(() => expect(result.current.isError).toBe(true));
expect(result.current.error).toBeTruthy();
});
});
describe('useUpdateSecurityNotificationSettings', () => {
const mockSettings: notificationsApi.SecurityNotificationSettings = {
enabled: true,
min_log_level: 'warn',
notify_waf_blocks: true,
notify_acl_denials: true,
notify_rate_limit_hits: false,
};
beforeEach(() => {
vi.mocked(notificationsApi.getSecurityNotificationSettings).mockResolvedValue(mockSettings);
});
it('updates security notification settings', async () => {
const updatedSettings = { ...mockSettings, min_log_level: 'error' };
vi.mocked(notificationsApi.updateSecurityNotificationSettings).mockResolvedValue(
updatedSettings
);
const { result } = renderHook(() => useUpdateSecurityNotificationSettings(), {
wrapper: createWrapper(),
});
result.current.mutate({ min_log_level: 'error' });
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(notificationsApi.updateSecurityNotificationSettings).toHaveBeenCalledWith({
min_log_level: 'error',
});
});
it('performs optimistic update', async () => {
const updatedSettings = { ...mockSettings, enabled: false };
vi.mocked(notificationsApi.updateSecurityNotificationSettings).mockResolvedValue(
updatedSettings
);
// Pre-populate cache
queryClient.setQueryData(['security-notification-settings'], mockSettings);
const { result } = renderHook(() => useUpdateSecurityNotificationSettings(), {
wrapper: createWrapper(),
});
result.current.mutate({ enabled: false });
// Wait a bit for the optimistic update to take effect
await waitFor(() => {
const cachedData = queryClient.getQueryData(['security-notification-settings']);
expect(cachedData).toMatchObject({ enabled: false });
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
});
it('rolls back on error', async () => {
vi.mocked(notificationsApi.updateSecurityNotificationSettings).mockRejectedValue(
new Error('Update failed')
);
// Pre-populate cache
queryClient.setQueryData(['security-notification-settings'], mockSettings);
const { result } = renderHook(() => useUpdateSecurityNotificationSettings(), {
wrapper: createWrapper(),
});
result.current.mutate({ enabled: false });
await waitFor(() => expect(result.current.isError).toBe(true));
// Check that original data is restored
const cachedData = queryClient.getQueryData(['security-notification-settings']);
expect(cachedData).toEqual(mockSettings);
});
it('shows success toast on successful update', async () => {
const toast = await import('../../utils/toast');
const updatedSettings = { ...mockSettings, min_log_level: 'error' };
vi.mocked(notificationsApi.updateSecurityNotificationSettings).mockResolvedValue(
updatedSettings
);
const { result } = renderHook(() => useUpdateSecurityNotificationSettings(), {
wrapper: createWrapper(),
});
result.current.mutate({ min_log_level: 'error' });
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(toast.toast.success).toHaveBeenCalledWith('Notification settings updated');
});
it('shows error toast on failed update', async () => {
const toast = await import('../../utils/toast');
vi.mocked(notificationsApi.updateSecurityNotificationSettings).mockRejectedValue(
new Error('Update failed')
);
const { result } = renderHook(() => useUpdateSecurityNotificationSettings(), {
wrapper: createWrapper(),
});
result.current.mutate({ enabled: false });
await waitFor(() => expect(result.current.isError).toBe(true));
expect(toast.toast.error).toHaveBeenCalledWith('Update failed');
});
it('invalidates queries on success', async () => {
const updatedSettings = { ...mockSettings, min_log_level: 'error' };
vi.mocked(notificationsApi.updateSecurityNotificationSettings).mockResolvedValue(
updatedSettings
);
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
const { result } = renderHook(() => useUpdateSecurityNotificationSettings(), {
wrapper: createWrapper(),
});
result.current.mutate({ min_log_level: 'error' });
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(invalidateSpy).toHaveBeenCalledWith({
queryKey: ['security-notification-settings'],
});
});
it('handles updates with multiple fields', async () => {
const updatedSettings = {
...mockSettings,
enabled: false,
min_log_level: 'error',
webhook_url: 'https://new-webhook.com',
};
vi.mocked(notificationsApi.updateSecurityNotificationSettings).mockResolvedValue(
updatedSettings
);
const { result } = renderHook(() => useUpdateSecurityNotificationSettings(), {
wrapper: createWrapper(),
});
result.current.mutate({
enabled: false,
min_log_level: 'error',
webhook_url: 'https://new-webhook.com',
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(notificationsApi.updateSecurityNotificationSettings).toHaveBeenCalledWith({
enabled: false,
min_log_level: 'error',
webhook_url: 'https://new-webhook.com',
});
});
});
});