chore: Add tests for backup service, crowdsec startup, log service, and security headers

- Implement tests for BackupService to handle database extraction from backup archives with SHM and WAL entries.
- Add tests for BackupService to validate behavior when creating backups for non-SQLite databases and handling oversized database entries.
- Introduce tests for CrowdSec startup to ensure proper error handling during configuration creation.
- Enhance LogService tests to cover scenarios for skipping dot and empty directories and handling read directory errors.
- Add tests for SecurityHeadersService to ensure proper error handling during preset creation and updates.
- Update ProxyHostForm tests to include HSTS subdomains toggle and validation for port input handling.
- Enhance DNSProviders tests to validate manual challenge completion and error handling when no providers are available.
- Extend UsersPage tests to ensure fallback mechanisms for clipboard operations when the clipboard API fails.
This commit is contained in:
GitHub Actions
2026-02-17 19:13:28 +00:00
parent 9713908887
commit 2cad49de85
41 changed files with 4071 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'
import { render, screen, waitFor, within } from '@testing-library/react'
import { render, screen, waitFor, within, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { act } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
@@ -731,6 +731,33 @@ describe('ProxyHostForm', () => {
expect(blockExploitsCheckbox).toBeChecked()
await userEvent.click(blockExploitsCheckbox)
expect(blockExploitsCheckbox).not.toBeChecked()
// Toggle HSTS Subdomains (default is true)
const hstsSubdomainsCheckbox = screen.getByLabelText('HSTS Subdomains')
expect(hstsSubdomainsCheckbox).toBeChecked()
await userEvent.click(hstsSubdomainsCheckbox)
expect(hstsSubdomainsCheckbox).not.toBeChecked()
})
it('submits updated hsts_subdomains flag', async () => {
await renderWithClientAct(
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)
await userEvent.type(screen.getByPlaceholderText('My Service'), 'HSTS Toggle')
await userEvent.type(screen.getByPlaceholderText('example.com, www.example.com'), 'hsts.existing.com')
await userEvent.type(screen.getByLabelText(/^Host$/), '192.168.1.10')
const hstsSubdomainsCheckbox = screen.getByLabelText('HSTS Subdomains')
await userEvent.click(hstsSubdomainsCheckbox)
await userEvent.click(screen.getByText('Save'))
await waitFor(() => {
expect(mockOnSubmit).toHaveBeenCalledWith(expect.objectContaining({
hsts_subdomains: false,
}))
})
})
})
@@ -897,6 +924,25 @@ describe('ProxyHostForm', () => {
}))
})
})
it('renders and selects non-preset security header profile options', async () => {
const { useSecurityHeaderProfiles } = await import('../../hooks/useSecurityHeaders')
vi.mocked(useSecurityHeaderProfiles).mockReturnValue({
data: [
{ id: 100, name: 'Strict Profile', description: 'Very strict', security_score: 90, is_preset: true, preset_type: 'strict' },
{ id: 101, name: 'Custom Profile', description: 'Custom profile', security_score: 70, is_preset: false },
],
isLoading: false,
error: null,
} as unknown as ReturnType<typeof useSecurityHeaderProfiles>)
renderWithClient(
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)
await selectComboboxOption(/Security Headers/i, 'Custom Profile (Score: 70/100)')
expect(screen.getByRole('combobox', { name: /Security Headers/i })).toHaveTextContent('Custom Profile')
})
})
describe('Edit Mode vs Create Mode', () => {
@@ -1072,6 +1118,34 @@ describe('ProxyHostForm', () => {
})
describe('Port Input Handling', () => {
it('shows required-port validation branch when submit is triggered with empty port', async () => {
await renderWithClientAct(
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)
await userEvent.type(screen.getByPlaceholderText('My Service'), 'Port Required')
await userEvent.type(screen.getByPlaceholderText('example.com, www.example.com'), 'required.existing.com')
await userEvent.type(screen.getByLabelText(/^Host$/), '192.168.1.100')
const portInput = screen.getByLabelText(/^Port$/) as HTMLInputElement
await userEvent.clear(portInput)
const setCustomValiditySpy = vi.spyOn(HTMLInputElement.prototype, 'setCustomValidity')
const reportValiditySpy = vi.spyOn(HTMLInputElement.prototype, 'reportValidity').mockReturnValue(false)
const form = document.querySelector('form') as HTMLFormElement
fireEvent.submit(form)
await waitFor(() => {
expect(setCustomValiditySpy).toHaveBeenCalledWith('Port is required')
expect(reportValiditySpy).toHaveBeenCalled()
expect(mockOnSubmit).not.toHaveBeenCalled()
})
setCustomValiditySpy.mockRestore()
reportValiditySpy.mockRestore()
})
it('validates port number range', async () => {
await renderWithClientAct(
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
@@ -1092,6 +1166,87 @@ describe('ProxyHostForm', () => {
expect(portInput).toBeInvalid()
expect(mockOnSubmit).not.toHaveBeenCalled()
})
it('shows out-of-range validation branch when submit is triggered with invalid port', async () => {
await renderWithClientAct(
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)
await userEvent.type(screen.getByPlaceholderText('My Service'), 'Port Range Branch')
await userEvent.type(screen.getByPlaceholderText('example.com, www.example.com'), 'range.existing.com')
await userEvent.type(screen.getByLabelText(/^Host$/), '192.168.1.100')
const portInput = screen.getByLabelText(/^Port$/) as HTMLInputElement
await userEvent.clear(portInput)
await userEvent.type(portInput, '70000')
const setCustomValiditySpy = vi.spyOn(HTMLInputElement.prototype, 'setCustomValidity')
const reportValiditySpy = vi.spyOn(HTMLInputElement.prototype, 'reportValidity').mockReturnValue(false)
const form = document.querySelector('form') as HTMLFormElement
fireEvent.submit(form)
await waitFor(() => {
expect(setCustomValiditySpy).toHaveBeenCalledWith('Port must be between 1 and 65535')
expect(reportValiditySpy).toHaveBeenCalled()
expect(mockOnSubmit).not.toHaveBeenCalled()
})
setCustomValiditySpy.mockRestore()
reportValiditySpy.mockRestore()
})
})
describe('Remote Server Container Mapping', () => {
it('allows selecting a remote docker source option', async () => {
await renderWithClientAct(
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)
await selectComboboxOption('Source', 'Local Docker Registry (localhost)')
expect(screen.getByRole('combobox', { name: 'Source' })).toHaveTextContent('Local Docker Registry')
})
it('maps remote docker container to remote host and public port', async () => {
const { useDocker } = await import('../../hooks/useDocker')
vi.mocked(useDocker).mockReturnValue({
containers: [
{
id: 'remote-container-1',
names: ['remote-app'],
image: 'nginx:latest',
state: 'running',
status: 'Up 1 hour',
network: 'bridge',
ip: '172.18.0.10',
ports: [{ private_port: 80, public_port: 18080, type: 'tcp' }],
},
],
isLoading: false,
error: null,
refetch: vi.fn(),
})
renderWithClient(
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)
await userEvent.type(screen.getByLabelText(/^Name/), 'Remote Mapping')
await userEvent.type(screen.getByPlaceholderText('example.com, www.example.com'), 'remote.existing.com')
await selectComboboxOption('Source', 'Local Docker Registry (localhost)')
await selectComboboxOption('Containers', 'remote-app (nginx:latest)')
await userEvent.click(screen.getByText('Save'))
await waitFor(() => {
expect(mockOnSubmit).toHaveBeenCalledWith(expect.objectContaining({
forward_host: 'localhost',
forward_port: 18080,
}))
})
})
})
describe('Host and Port Combination', () => {