678 lines
21 KiB
TypeScript
678 lines
21 KiB
TypeScript
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { MemoryRouter } from 'react-router-dom';
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import userEvent from '@testing-library/user-event';
|
|
import SecurityHeaders from '../../pages/SecurityHeaders';
|
|
import { securityHeadersApi, SecurityHeaderProfile } from '../../api/securityHeaders';
|
|
import { createBackup } from '../../api/backups';
|
|
|
|
vi.mock('../../api/securityHeaders');
|
|
vi.mock('../../api/backups');
|
|
vi.mock('react-hot-toast');
|
|
|
|
const createWrapper = () => {
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
|
|
return ({ children }: { children: React.ReactNode }) => (
|
|
<QueryClientProvider client={queryClient}>
|
|
<MemoryRouter>{children}</MemoryRouter>
|
|
</QueryClientProvider>
|
|
);
|
|
};
|
|
|
|
describe('SecurityHeaders', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should render loading state', () => {
|
|
vi.mocked(securityHeadersApi.listProfiles).mockImplementation(() => new Promise(() => {}));
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
expect(screen.getByText('Security Headers')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render empty state', async () => {
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue([]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('No custom profiles yet')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render list of profiles', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Profile 1',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Profile 2',
|
|
is_preset: false,
|
|
security_score: 90,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Profile 1')).toBeInTheDocument();
|
|
expect(screen.getByText('Profile 2')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render presets', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Basic Security',
|
|
description: 'Essential headers',
|
|
is_preset: true,
|
|
preset_type: 'basic',
|
|
security_score: 65,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Strict Security',
|
|
description: 'Strong security',
|
|
is_preset: true,
|
|
preset_type: 'strict',
|
|
security_score: 85,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Basic Security')).toBeInTheDocument();
|
|
expect(screen.getByText('Strict Security')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should open create form dialog', async () => {
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue([]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('button', { name: /Create Profile/ })).toBeInTheDocument();
|
|
});
|
|
|
|
const createButton = screen.getAllByRole('button', { name: /Create Profile/ })[0];
|
|
fireEvent.click(createButton);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Create Security Header Profile/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should open edit dialog', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Test Profile',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
vi.mocked(securityHeadersApi.calculateScore).mockResolvedValue({
|
|
score: 85,
|
|
max_score: 100,
|
|
breakdown: {},
|
|
suggestions: [],
|
|
});
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Profile')).toBeInTheDocument();
|
|
});
|
|
|
|
const editButton = screen.getByRole('button', { name: /Edit/ });
|
|
fireEvent.click(editButton);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Edit Security Header Profile/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should clone profile', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Original Profile',
|
|
description: 'Test description',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
hsts_enabled: true,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
vi.mocked(securityHeadersApi.createProfile).mockResolvedValue({
|
|
id: 2,
|
|
name: 'Original Profile (Copy)',
|
|
security_score: 85,
|
|
} as SecurityHeaderProfile);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Original Profile')).toBeInTheDocument();
|
|
});
|
|
|
|
const buttons = screen.getAllByRole('button');
|
|
const cloneButton = buttons.find(btn => btn.querySelector('.lucide-copy'));
|
|
if (cloneButton) {
|
|
fireEvent.click(cloneButton);
|
|
}
|
|
|
|
await waitFor(() => {
|
|
expect(securityHeadersApi.createProfile).toHaveBeenCalled();
|
|
});
|
|
|
|
const createCall = vi.mocked(securityHeadersApi.createProfile).mock.calls[0][0];
|
|
expect(createCall.name).toBe('Original Profile (Copy)');
|
|
});
|
|
|
|
it('should delete profile with backup', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Test Profile',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
vi.mocked(createBackup).mockResolvedValue({ filename: 'backup.tar.gz' });
|
|
vi.mocked(securityHeadersApi.deleteProfile).mockResolvedValue(undefined);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Profile')).toBeInTheDocument();
|
|
});
|
|
|
|
// Click delete button
|
|
const buttons = screen.getAllByRole('button');
|
|
const deleteButton = buttons.find(btn => btn.querySelector('.lucide-trash-2, .lucide-trash'));
|
|
if (deleteButton) {
|
|
fireEvent.click(deleteButton);
|
|
}
|
|
|
|
// Confirm deletion - wait for the dialog to appear
|
|
await waitFor(() => {
|
|
const headings = screen.getAllByText(/Confirm Deletion/i);
|
|
expect(headings.length).toBeGreaterThan(0);
|
|
}, { timeout: 2000 });
|
|
|
|
const confirmButton = screen.getByRole('button', { name: /Delete/i });
|
|
fireEvent.click(confirmButton);
|
|
|
|
await waitFor(() => {
|
|
expect(createBackup).toHaveBeenCalled();
|
|
expect(securityHeadersApi.deleteProfile).toHaveBeenCalledWith(1);
|
|
});
|
|
});
|
|
|
|
it('should separate quick presets from custom profiles', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Custom Profile',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Basic Security',
|
|
is_preset: true,
|
|
preset_type: 'basic',
|
|
security_score: 65,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('System Profiles (Read-Only)')).toBeInTheDocument();
|
|
expect(screen.getByText('Custom Profiles')).toBeInTheDocument();
|
|
});
|
|
|
|
// System profiles should have View and Clone buttons
|
|
const presetCard = screen.getByText('Basic Security').closest('div');
|
|
expect(presetCard).toBeInTheDocument();
|
|
|
|
// Custom profile should have Edit button
|
|
const customCard = screen.getByText('Custom Profile').closest('div');
|
|
expect(customCard?.textContent).toContain('Custom Profile');
|
|
});
|
|
|
|
it('should display security scores', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'High Score Profile',
|
|
is_preset: false,
|
|
security_score: 95,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('95')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
// Additional coverage tests for Phase 3
|
|
|
|
it('should display preset tooltip information', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Basic Security',
|
|
is_preset: true,
|
|
preset_type: 'basic',
|
|
security_score: 65,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
const user = userEvent.setup();
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Basic Security')).toBeInTheDocument();
|
|
});
|
|
|
|
// Find info icon and hover
|
|
const infoButtons = screen.getAllByRole('button').filter(btn => {
|
|
const svg = btn.querySelector('svg');
|
|
return svg?.classList.contains('lucide-info');
|
|
});
|
|
|
|
if (infoButtons.length > 0) {
|
|
await user.hover(infoButtons[0]);
|
|
}
|
|
});
|
|
|
|
it('should show view button for preset profiles', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Strict Security',
|
|
is_preset: true,
|
|
preset_type: 'strict',
|
|
security_score: 95,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('button', { name: /View/i })).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should close form when dialog is dismissed', async () => {
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue([]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('button', { name: /Create Profile/ })).toBeInTheDocument();
|
|
});
|
|
|
|
const createButton = screen.getAllByRole('button', { name: /Create Profile/ })[0];
|
|
fireEvent.click(createButton);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Create Security Header Profile/)).toBeInTheDocument();
|
|
});
|
|
|
|
// Close dialog by pressing escape or clicking outside
|
|
const dialog = screen.getByRole('dialog');
|
|
expect(dialog).toBeInTheDocument();
|
|
});
|
|
|
|
it('should sort preset profiles by security score', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Paranoid Security',
|
|
is_preset: true,
|
|
preset_type: 'paranoid',
|
|
security_score: 100,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Basic Security',
|
|
is_preset: true,
|
|
preset_type: 'basic',
|
|
security_score: 65,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'API Friendly',
|
|
is_preset: true,
|
|
preset_type: 'api-friendly',
|
|
security_score: 75,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Basic Security')).toBeInTheDocument();
|
|
});
|
|
|
|
// Verify all presets are displayed
|
|
expect(screen.getByText('Paranoid Security')).toBeInTheDocument();
|
|
expect(screen.getByText('API Friendly')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display updated date for profiles', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Test Profile',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
updated_at: '2025-01-20T10:30:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Updated/i)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle clone button for custom profiles', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Custom Profile',
|
|
description: 'My custom config',
|
|
is_preset: false,
|
|
security_score: 80,
|
|
hsts_enabled: true,
|
|
hsts_max_age: 31536000,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
vi.mocked(securityHeadersApi.createProfile).mockResolvedValue({
|
|
id: 2,
|
|
name: 'Custom Profile (Copy)',
|
|
security_score: 80,
|
|
} as SecurityHeaderProfile);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Custom Profile')).toBeInTheDocument();
|
|
});
|
|
|
|
const buttons = screen.getAllByRole('button');
|
|
const cloneButton = buttons.find(btn => btn.querySelector('.lucide-copy'));
|
|
if (cloneButton) {
|
|
fireEvent.click(cloneButton);
|
|
}
|
|
|
|
await waitFor(() => {
|
|
expect(securityHeadersApi.createProfile).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('should display profile descriptions', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Test Profile',
|
|
description: 'This is a test profile description',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('This is a test profile description')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle delete confirmation cancellation', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Test Profile',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Profile')).toBeInTheDocument();
|
|
});
|
|
|
|
// Click delete button
|
|
const buttons = screen.getAllByRole('button');
|
|
const deleteButton = buttons.find(btn => btn.querySelector('.lucide-trash-2, .lucide-trash'));
|
|
if (deleteButton) {
|
|
fireEvent.click(deleteButton);
|
|
}
|
|
|
|
// Wait for confirmation dialog
|
|
await waitFor(() => {
|
|
const headings = screen.getAllByText(/Confirm Deletion/i);
|
|
expect(headings.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
// Click cancel instead of delete
|
|
const cancelButton = screen.getByRole('button', { name: /Cancel/i });
|
|
fireEvent.click(cancelButton);
|
|
|
|
await waitFor(() => {
|
|
expect(securityHeadersApi.deleteProfile).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('should show info alert with security configuration message', async () => {
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue([]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Secure Your Applications/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/Security headers protect against common web vulnerabilities/i)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should display all three action buttons for custom profiles', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Custom Profile',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Custom Profile')).toBeInTheDocument();
|
|
});
|
|
|
|
// Should have Edit button
|
|
expect(screen.getByRole('button', { name: /Edit/i })).toBeInTheDocument();
|
|
|
|
// Should have Clone button (icon only)
|
|
const buttons = screen.getAllByRole('button');
|
|
const cloneButton = buttons.find(btn => btn.querySelector('.lucide-copy'));
|
|
expect(cloneButton).toBeDefined();
|
|
|
|
// Should have Delete button (icon only)
|
|
const deleteButton = buttons.find(btn => btn.querySelector('.lucide-trash-2, .lucide-trash'));
|
|
expect(deleteButton).toBeDefined();
|
|
});
|
|
|
|
it('should handle profile update submission', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Test Profile',
|
|
is_preset: false,
|
|
security_score: 85,
|
|
hsts_enabled: true,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
vi.mocked(securityHeadersApi.updateProfile).mockResolvedValue({
|
|
id: 1,
|
|
name: 'Updated Profile',
|
|
security_score: 90,
|
|
} as SecurityHeaderProfile);
|
|
vi.mocked(securityHeadersApi.calculateScore).mockResolvedValue({
|
|
score: 85,
|
|
max_score: 100,
|
|
breakdown: {},
|
|
suggestions: [],
|
|
});
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Profile')).toBeInTheDocument();
|
|
});
|
|
|
|
const editButton = screen.getByRole('button', { name: /Edit/i });
|
|
fireEvent.click(editButton);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Edit Security Header Profile/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should display system profiles section title', async () => {
|
|
const mockProfiles = [
|
|
{
|
|
id: 1,
|
|
name: 'Basic Security',
|
|
is_preset: true,
|
|
preset_type: 'basic',
|
|
security_score: 65,
|
|
updated_at: '2025-12-18T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('System Profiles (Read-Only)')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render empty state action in custom profiles section', async () => {
|
|
vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue([]);
|
|
vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]);
|
|
|
|
render(<SecurityHeaders />, { wrapper: createWrapper() });
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('No custom profiles yet')).toBeInTheDocument();
|
|
});
|
|
|
|
const createButtons = screen.getAllByRole('button', { name: /Create Profile/i });
|
|
expect(createButtons.length).toBeGreaterThan(0);
|
|
});
|
|
});
|