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 { AxiosError } from 'axios'
import { screen, waitFor, act, cleanup, within } from '@testing-library/react'
import { screen, waitFor, act, cleanup, within, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { QueryClient } from '@tanstack/react-query'
import { describe, it, expect, vi, beforeEach } from 'vitest'
@@ -288,6 +288,45 @@ describe('CrowdSecConfig coverage', () => {
await waitFor(() => expect(screen.getByTestId('preset-apply-info')).toHaveTextContent('Backup: /tmp/backup.tar.gz'))
})
it('supports keyboard selection for preset cards (Enter and Space)', async () => {
vi.mocked(presetsApi.listCrowdsecPresets).mockResolvedValueOnce({
presets: [
{
slug: CROWDSEC_PRESETS[0].slug,
title: CROWDSEC_PRESETS[0].title,
summary: CROWDSEC_PRESETS[0].description,
source: 'hub',
requires_hub: false,
available: true,
cached: false,
cache_key: 'cache-a',
},
{
slug: CROWDSEC_PRESETS[1].slug,
title: CROWDSEC_PRESETS[1].title,
summary: CROWDSEC_PRESETS[1].description,
source: 'hub',
requires_hub: false,
available: true,
cached: false,
cache_key: 'cache-b',
},
],
})
await renderPage()
const firstCard = await screen.findByRole('button', { name: new RegExp(CROWDSEC_PRESETS[0].title, 'i') })
const secondCard = await screen.findByRole('button', { name: new RegExp(CROWDSEC_PRESETS[1].title, 'i') })
firstCard.focus()
await userEvent.keyboard('{Enter}')
secondCard.focus()
await userEvent.keyboard(' ')
await waitFor(() => expect(presetsApi.pullCrowdsecPreset).toHaveBeenCalledTimes(2))
})
it('falls back to local apply on 501 and covers validation/hub/offline branches', async () => {
vi.mocked(crowdsecApi.writeCrowdsecFile).mockResolvedValue({})
vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(501, 'not implemented'))
@@ -460,6 +499,79 @@ describe('CrowdSecConfig coverage', () => {
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('unban fail'))
})
it('supports ban modal click and keyboard interactions', async () => {
await renderPage()
await userEvent.click(screen.getByRole('button', { name: /Ban IP/ }))
expect(await screen.findByText('Ban IP Address')).toBeInTheDocument()
const banDialog = screen.getByRole('dialog', { name: 'Ban IP Address' })
const banOverlay = banDialog.parentElement?.querySelector('[class*="bg-black/60"]') as HTMLElement
fireEvent.click(banOverlay)
await waitFor(() => expect(screen.queryByText('Ban IP Address')).not.toBeInTheDocument())
await userEvent.click(screen.getByRole('button', { name: /Ban IP/ }))
const modalContainer = screen.getByRole('dialog', { name: 'Ban IP Address' })
const ipInput = within(modalContainer).getByPlaceholderText('192.168.1.100')
await userEvent.type(ipInput, '9.9.9.9')
await userEvent.keyboard('{Control>}{Enter}{/Control}')
await waitFor(() => expect(crowdsecApi.banIP).toHaveBeenCalledWith('9.9.9.9', '24h', ''))
await userEvent.click(screen.getByRole('button', { name: /Ban IP/ }))
const secondModalContainer = screen.getByRole('dialog', { name: 'Ban IP Address' })
const secondIpInput = within(secondModalContainer).getByPlaceholderText('192.168.1.100')
await userEvent.type(secondIpInput, '8.8.8.8')
await userEvent.keyboard('{Enter}')
await waitFor(() => expect(crowdsecApi.banIP).toHaveBeenCalledWith('8.8.8.8', '24h', ''))
await userEvent.click(screen.getByRole('button', { name: /Ban IP/ }))
const thirdModalContainer = screen.getByRole('dialog', { name: 'Ban IP Address' })
const thirdIpInput = within(thirdModalContainer).getByPlaceholderText('192.168.1.100')
await userEvent.type(thirdIpInput, '8.8.8.8')
const reasonInput = within(thirdModalContainer).getByLabelText('Reason')
await userEvent.type(reasonInput, 'manual reason{Enter}')
await waitFor(() => expect(crowdsecApi.banIP).toHaveBeenCalledWith('8.8.8.8', '24h', 'manual reason'))
})
it('supports unban modal overlay, Escape, Enter, and cancel button', async () => {
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockResolvedValueOnce({
decisions: [
{ id: '1', ip: '7.7.7.7', reason: 'bot', duration: '24h', created_at: '2024-01-01T00:00:00Z', source: 'manual' },
],
})
await renderPage()
await userEvent.click(await screen.findByRole('button', { name: 'Unban' }))
expect(await screen.findByText('Confirm Unban')).toBeInTheDocument()
const unbanDialog = screen.getByRole('dialog', { name: 'Confirm Unban' })
const unbanOverlay = unbanDialog.parentElement?.querySelector('[class*="bg-black/60"]') as HTMLElement
fireEvent.click(unbanOverlay)
await waitFor(() => expect(screen.queryByText('Confirm Unban')).not.toBeInTheDocument())
await userEvent.click(await screen.findByRole('button', { name: 'Unban' }))
expect(await screen.findByText('Confirm Unban')).toBeInTheDocument()
await userEvent.keyboard('{Escape}')
await waitFor(() => expect(screen.queryByText('Confirm Unban')).not.toBeInTheDocument())
await userEvent.click(await screen.findByRole('button', { name: 'Unban' }))
expect(await screen.findByText('Confirm Unban')).toBeInTheDocument()
await userEvent.keyboard('{Enter}')
await waitFor(() => expect(crowdsecApi.unbanIP).toHaveBeenCalledWith('7.7.7.7'))
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockResolvedValueOnce({
decisions: [
{ id: '1', ip: '7.7.7.7', reason: 'bot', duration: '24h', created_at: '2024-01-01T00:00:00Z', source: 'manual' },
],
})
await renderPage()
await userEvent.click(await screen.findByRole('button', { name: 'Unban' }))
const confirmContainer = screen.getByRole('dialog', { name: 'Confirm Unban' })
await userEvent.click(within(confirmContainer).getByRole('button', { name: 'Cancel' }))
await waitFor(() => expect(screen.queryByText('Confirm Unban')).not.toBeInTheDocument())
})
it('bans and unbans IPs with overlay messaging', async () => {
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockResolvedValue({
decisions: [