chore: clean cache
This commit is contained in:
@@ -1,555 +0,0 @@
|
||||
import { AxiosError } from 'axios'
|
||||
import { screen, waitFor, act, cleanup, within } 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'
|
||||
import CrowdSecConfig from '../CrowdSecConfig'
|
||||
import * as securityApi from '../../api/security'
|
||||
import * as crowdsecApi from '../../api/crowdsec'
|
||||
import * as presetsApi from '../../api/presets'
|
||||
import * as backupsApi from '../../api/backups'
|
||||
import * as settingsApi from '../../api/settings'
|
||||
import { CROWDSEC_PRESETS } from '../../data/crowdsecPresets'
|
||||
import { renderWithQueryClient, createTestQueryClient } from '../../test-utils/renderWithQueryClient'
|
||||
import { toast } from '../../utils/toast'
|
||||
import * as exportUtils from '../../utils/crowdsecExport'
|
||||
|
||||
vi.mock('../../api/security')
|
||||
vi.mock('../../api/crowdsec')
|
||||
vi.mock('../../api/presets')
|
||||
vi.mock('../../api/backups')
|
||||
vi.mock('../../api/settings')
|
||||
vi.mock('../../utils/crowdsecExport', () => ({
|
||||
buildCrowdsecExportFilename: vi.fn(() => 'crowdsec-default.tar.gz'),
|
||||
promptCrowdsecFilename: vi.fn(() => 'crowdsec.tar.gz'),
|
||||
downloadCrowdsecExport: vi.fn(),
|
||||
}))
|
||||
vi.mock('../../utils/toast', () => ({
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const baseStatus = {
|
||||
cerberus: { enabled: true },
|
||||
crowdsec: { enabled: true, mode: 'local' as const, api_url: '' },
|
||||
waf: { enabled: true, mode: 'enabled' as const },
|
||||
rate_limit: { enabled: true },
|
||||
acl: { enabled: true },
|
||||
}
|
||||
|
||||
const disabledStatus = {
|
||||
...baseStatus,
|
||||
crowdsec: { ...baseStatus.crowdsec, enabled: true, mode: 'disabled' as const },
|
||||
}
|
||||
|
||||
const presetFromCatalog = CROWDSEC_PRESETS[0]
|
||||
|
||||
const axiosError = (status: number, message: string, data?: Record<string, unknown>) =>
|
||||
new AxiosError(message, undefined, undefined, undefined, {
|
||||
status,
|
||||
statusText: String(status),
|
||||
headers: {},
|
||||
config: {},
|
||||
data: data ?? { error: message },
|
||||
} as never)
|
||||
|
||||
const defaultFileList = ['acquis.yaml', 'collections.yaml']
|
||||
|
||||
const renderPage = async (client?: QueryClient) => {
|
||||
const result = renderWithQueryClient(<CrowdSecConfig />, { client })
|
||||
await waitFor(() => screen.getByText('CrowdSec Configuration'))
|
||||
return result
|
||||
}
|
||||
|
||||
describe('CrowdSecConfig coverage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(baseStatus)
|
||||
vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: defaultFileList })
|
||||
vi.mocked(crowdsecApi.readCrowdsecFile).mockResolvedValue({ content: 'file-content' })
|
||||
vi.mocked(crowdsecApi.writeCrowdsecFile).mockResolvedValue(undefined)
|
||||
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockResolvedValue({ decisions: [] })
|
||||
vi.mocked(crowdsecApi.banIP).mockResolvedValue(undefined)
|
||||
vi.mocked(crowdsecApi.unbanIP).mockResolvedValue(undefined)
|
||||
vi.mocked(crowdsecApi.exportCrowdsecConfig).mockResolvedValue(new Blob(['data']))
|
||||
vi.mocked(crowdsecApi.importCrowdsecConfig).mockResolvedValue(undefined)
|
||||
vi.mocked(presetsApi.listCrowdsecPresets).mockResolvedValue({
|
||||
presets: [
|
||||
{
|
||||
slug: presetFromCatalog.slug,
|
||||
title: presetFromCatalog.title,
|
||||
summary: presetFromCatalog.description,
|
||||
source: 'hub',
|
||||
requires_hub: false,
|
||||
available: true,
|
||||
cached: false,
|
||||
cache_key: 'cache-123',
|
||||
etag: 'etag-123',
|
||||
retrieved_at: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
],
|
||||
})
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockResolvedValue({
|
||||
status: 'pulled',
|
||||
slug: presetFromCatalog.slug,
|
||||
preview: presetFromCatalog.content,
|
||||
cache_key: 'cache-123',
|
||||
etag: 'etag-123',
|
||||
retrieved_at: '2024-01-01T00:00:00Z',
|
||||
source: 'hub',
|
||||
})
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockResolvedValue({
|
||||
status: 'applied',
|
||||
backup: '/tmp/backup.tar.gz',
|
||||
reload_hint: true,
|
||||
used_cscli: true,
|
||||
cache_key: 'cache-123',
|
||||
slug: presetFromCatalog.slug,
|
||||
})
|
||||
vi.mocked(presetsApi.getCrowdsecPresetCache).mockResolvedValue({
|
||||
preview: 'cached-preview',
|
||||
cache_key: 'cache-123',
|
||||
etag: 'etag-123',
|
||||
})
|
||||
vi.mocked(backupsApi.createBackup).mockResolvedValue({ filename: 'backup.tar.gz' })
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
})
|
||||
|
||||
it('renders loading and error boundaries', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockReturnValue(new Promise(() => {}))
|
||||
renderWithQueryClient(<CrowdSecConfig />)
|
||||
expect(await screen.findByText('Loading CrowdSec configuration...')).toBeInTheDocument()
|
||||
|
||||
cleanup()
|
||||
|
||||
vi.mocked(securityApi.getSecurityStatus).mockRejectedValue(new Error('boom'))
|
||||
renderWithQueryClient(<CrowdSecConfig />)
|
||||
expect(await screen.findByText(/Failed to load security status/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('handles missing status and missing crowdsec sections', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockRejectedValueOnce(new Error('data is undefined'))
|
||||
renderWithQueryClient(<CrowdSecConfig />)
|
||||
expect(await screen.findByText(/Failed to load security status/)).toBeInTheDocument()
|
||||
|
||||
cleanup()
|
||||
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValueOnce({ cerberus: { enabled: true } } as never)
|
||||
renderWithQueryClient(<CrowdSecConfig />)
|
||||
expect(await screen.findByText('CrowdSec configuration not found in security status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders disabled mode message and bans control disabled', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(disabledStatus)
|
||||
await renderPage(createTestQueryClient())
|
||||
expect(screen.getByText('Enable CrowdSec to manage banned IPs')).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /Ban IP/ })).toBeDisabled()
|
||||
})
|
||||
|
||||
it('toggles mode success and error', async () => {
|
||||
await renderPage()
|
||||
const toggle = screen.getByTestId('crowdsec-mode-toggle')
|
||||
await userEvent.click(toggle)
|
||||
await waitFor(() => expect(settingsApi.updateSetting).toHaveBeenCalledWith('security.crowdsec.mode', 'disabled', 'security', 'string'))
|
||||
expect(toast.success).toHaveBeenCalledWith('CrowdSec disabled')
|
||||
|
||||
vi.mocked(settingsApi.updateSetting).mockRejectedValueOnce(new Error('nope'))
|
||||
await userEvent.click(toggle)
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('nope'))
|
||||
})
|
||||
|
||||
it('guards import without a file and shows error on import failure', async () => {
|
||||
await renderPage()
|
||||
const importBtn = screen.getByTestId('import-btn')
|
||||
await userEvent.click(importBtn)
|
||||
expect(backupsApi.createBackup).not.toHaveBeenCalled()
|
||||
|
||||
const fileInput = screen.getByTestId('import-file') as HTMLInputElement
|
||||
const file = new File(['data'], 'cfg.tar.gz')
|
||||
await userEvent.upload(fileInput, file)
|
||||
vi.mocked(crowdsecApi.importCrowdsecConfig).mockRejectedValueOnce(new Error('bad import'))
|
||||
await userEvent.click(importBtn)
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('bad import'))
|
||||
})
|
||||
|
||||
it('imports configuration after creating a backup', async () => {
|
||||
await renderPage()
|
||||
const fileInput = screen.getByTestId('import-file') as HTMLInputElement
|
||||
await userEvent.upload(fileInput, new File(['data'], 'cfg.tar.gz'))
|
||||
await userEvent.click(screen.getByTestId('import-btn'))
|
||||
await waitFor(() => expect(backupsApi.createBackup).toHaveBeenCalled())
|
||||
await waitFor(() => expect(crowdsecApi.importCrowdsecConfig).toHaveBeenCalled())
|
||||
})
|
||||
|
||||
it('exports configuration success and failure', async () => {
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Export' }))
|
||||
await waitFor(() => expect(crowdsecApi.exportCrowdsecConfig).toHaveBeenCalled())
|
||||
expect(exportUtils.downloadCrowdsecExport).toHaveBeenCalled()
|
||||
expect(toast.success).toHaveBeenCalledWith('CrowdSec configuration exported')
|
||||
|
||||
vi.mocked(exportUtils.promptCrowdsecFilename).mockReturnValueOnce('crowdsec.tar.gz')
|
||||
vi.mocked(crowdsecApi.exportCrowdsecConfig).mockRejectedValueOnce(new Error('fail'))
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Export' }))
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('Failed to export CrowdSec configuration'))
|
||||
})
|
||||
|
||||
it('auto-selects first preset and pulls preview', async () => {
|
||||
await renderPage()
|
||||
const select = screen.getByTestId('preset-select') as HTMLSelectElement
|
||||
expect(select.value).toBe(presetFromCatalog.slug)
|
||||
await waitFor(() => expect(presetsApi.pullCrowdsecPreset).toHaveBeenCalledWith(presetFromCatalog.slug))
|
||||
const previewText = screen.getByTestId('preset-preview').textContent?.replace(/\s+/g, ' ')
|
||||
expect(previewText).toContain('crowdsecurity/http-cve')
|
||||
expect(screen.getByTestId('preset-meta')).toHaveTextContent('cache-123')
|
||||
})
|
||||
|
||||
it('handles pull validation, hub unavailable, and generic errors', async () => {
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockRejectedValueOnce(axiosError(400, 'slug invalid', { error: 'slug invalid' }))
|
||||
await renderPage()
|
||||
expect(await screen.findByTestId('preset-validation-error')).toHaveTextContent('slug invalid')
|
||||
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockRejectedValueOnce(axiosError(503, 'hub down', { error: 'hub down' }))
|
||||
await userEvent.click(screen.getByText('Pull Preview'))
|
||||
await waitFor(() => expect(screen.getByTestId('preset-hub-unavailable')).toBeInTheDocument())
|
||||
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockRejectedValueOnce(axiosError(500, 'boom', { error: 'boom' }))
|
||||
await userEvent.click(screen.getByText('Pull Preview'))
|
||||
await waitFor(() => expect(screen.getByTestId('preset-status')).toHaveTextContent('boom'))
|
||||
})
|
||||
|
||||
it('loads cached preview and reports cache errors', async () => {
|
||||
vi.mocked(presetsApi.listCrowdsecPresets).mockResolvedValueOnce({
|
||||
presets: [
|
||||
{
|
||||
slug: presetFromCatalog.slug,
|
||||
title: presetFromCatalog.title,
|
||||
summary: presetFromCatalog.description,
|
||||
source: 'hub',
|
||||
requires_hub: false,
|
||||
available: true,
|
||||
cached: true,
|
||||
cache_key: 'cache-123',
|
||||
etag: 'etag-123',
|
||||
retrieved_at: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
],
|
||||
})
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getByText('Pull Preview'))
|
||||
await waitFor(() => {
|
||||
const preview = screen.getByTestId('preset-preview').textContent?.replace(/\s+/g, ' ')
|
||||
expect(preview).toContain('crowdsecurity/http-cve')
|
||||
})
|
||||
await userEvent.click(screen.getByText('Load cached preview'))
|
||||
await waitFor(() => expect(screen.getByTestId('preset-preview')).toHaveTextContent('cached-preview'))
|
||||
|
||||
vi.mocked(presetsApi.getCrowdsecPresetCache).mockRejectedValueOnce(axiosError(500, 'cache-miss'))
|
||||
await userEvent.click(screen.getByText('Load cached preview'))
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('cache-miss'))
|
||||
})
|
||||
|
||||
it('sets apply info on backend success', async () => {
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getByTestId('apply-preset-btn'))
|
||||
await waitFor(() => expect(screen.getByTestId('preset-apply-info')).toHaveTextContent('Backup: /tmp/backup.tar.gz'))
|
||||
})
|
||||
|
||||
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'))
|
||||
await renderPage()
|
||||
const applyBtn = screen.getByTestId('apply-preset-btn')
|
||||
await userEvent.click(applyBtn)
|
||||
await waitFor(() => expect(toast.info).toHaveBeenCalledWith('Preset apply is not available on the server; applying locally instead'))
|
||||
await waitFor(() => expect(crowdsecApi.writeCrowdsecFile).toHaveBeenCalled())
|
||||
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(400, 'bad', { error: 'validation failed' }))
|
||||
await userEvent.click(applyBtn)
|
||||
await waitFor(() => expect(screen.getByTestId('preset-validation-error')).toHaveTextContent('validation failed'))
|
||||
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(503, 'hub'))
|
||||
await userEvent.click(applyBtn)
|
||||
await waitFor(() => expect(screen.getByTestId('preset-hub-unavailable')).toBeInTheDocument())
|
||||
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(500, 'not cached', { error: 'Pull the preset first' }))
|
||||
await userEvent.click(applyBtn)
|
||||
await waitFor(() => expect(screen.getByTestId('preset-validation-error')).toHaveTextContent('Preset must be pulled'))
|
||||
})
|
||||
|
||||
it('records backup info on apply failure and generic errors', async () => {
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(500, 'failed', { error: 'boom', backup: '/tmp/backup' }))
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getByTestId('apply-preset-btn'))
|
||||
await waitFor(() => expect(screen.getByTestId('preset-apply-info')).toHaveTextContent('/tmp/backup'))
|
||||
|
||||
cleanup()
|
||||
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(new Error('unexpected'))
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getByTestId('apply-preset-btn'))
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('Failed to apply preset'))
|
||||
})
|
||||
|
||||
it('disables apply when hub is unavailable for hub-only preset', async () => {
|
||||
vi.mocked(presetsApi.listCrowdsecPresets).mockResolvedValueOnce({
|
||||
presets: [
|
||||
{
|
||||
slug: 'hub-only',
|
||||
title: 'Hub Only',
|
||||
summary: 'needs hub',
|
||||
source: 'hub',
|
||||
requires_hub: true,
|
||||
available: true,
|
||||
cached: true,
|
||||
cache_key: 'cache-hub',
|
||||
etag: 'etag-hub',
|
||||
},
|
||||
],
|
||||
})
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockRejectedValueOnce(axiosError(503, 'hub'))
|
||||
await renderPage()
|
||||
await waitFor(() => expect(screen.getByTestId('preset-hub-unavailable')).toBeInTheDocument())
|
||||
expect((screen.getByTestId('apply-preset-btn') as HTMLButtonElement).disabled).toBe(true)
|
||||
})
|
||||
|
||||
it('guards local apply prerequisites and succeeds when content exists', async () => {
|
||||
vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValueOnce({ files: [] })
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(501, 'not implemented'))
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getByTestId('apply-preset-btn'))
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('Select a configuration file to apply the preset'))
|
||||
|
||||
cleanup()
|
||||
vi.mocked(toast.error).mockClear()
|
||||
|
||||
vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: ['acquis.yaml'] })
|
||||
vi.mocked(presetsApi.listCrowdsecPresets).mockResolvedValue({
|
||||
presets: [
|
||||
{
|
||||
slug: 'custom-empty',
|
||||
title: 'Empty',
|
||||
summary: 'empty preset',
|
||||
source: 'hub',
|
||||
requires_hub: false,
|
||||
available: true,
|
||||
cached: false,
|
||||
cache_key: 'cache-empty',
|
||||
etag: 'etag-empty',
|
||||
},
|
||||
],
|
||||
})
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockResolvedValue({
|
||||
status: 'pulled',
|
||||
slug: 'custom-empty',
|
||||
preview: '',
|
||||
cache_key: 'cache-empty',
|
||||
})
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(501, 'not implemented'))
|
||||
await renderPage()
|
||||
await userEvent.selectOptions(screen.getByTestId('crowdsec-file-select'), 'acquis.yaml')
|
||||
await userEvent.click(screen.getByTestId('apply-preset-btn'))
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('Preset preview is unavailable; retry pulling before applying'))
|
||||
|
||||
cleanup()
|
||||
vi.mocked(toast.error).mockClear()
|
||||
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockResolvedValue({
|
||||
status: 'pulled',
|
||||
slug: presetFromCatalog.slug,
|
||||
preview: 'content',
|
||||
cache_key: 'cache-123',
|
||||
})
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(501, 'not implemented'))
|
||||
vi.mocked(crowdsecApi.writeCrowdsecFile).mockResolvedValue({})
|
||||
await renderPage()
|
||||
await userEvent.selectOptions(screen.getByTestId('crowdsec-file-select'), 'acquis.yaml')
|
||||
await userEvent.click(screen.getByTestId('apply-preset-btn'))
|
||||
await waitFor(() => expect(crowdsecApi.writeCrowdsecFile).toHaveBeenCalled())
|
||||
})
|
||||
|
||||
it('reads, edits, saves, and closes files', async () => {
|
||||
await renderPage()
|
||||
await userEvent.selectOptions(screen.getByTestId('crowdsec-file-select'), 'acquis.yaml')
|
||||
await waitFor(() => expect(crowdsecApi.readCrowdsecFile).toHaveBeenCalledWith('acquis.yaml'))
|
||||
const textarea = screen.getByRole('textbox') as HTMLTextAreaElement
|
||||
expect(textarea.value).toBe('file-content')
|
||||
await userEvent.clear(textarea)
|
||||
await userEvent.type(textarea, 'updated')
|
||||
await userEvent.click(screen.getByText('Save'))
|
||||
await waitFor(() => expect(backupsApi.createBackup).toHaveBeenCalled())
|
||||
await waitFor(() => expect(crowdsecApi.writeCrowdsecFile).toHaveBeenCalledWith('acquis.yaml', 'updated'))
|
||||
|
||||
await userEvent.click(screen.getByText('Close'))
|
||||
expect((screen.getByTestId('crowdsec-file-select') as HTMLSelectElement).value).toBe('')
|
||||
})
|
||||
|
||||
it('shows decisions table, handles loading/error/empty states, and unban errors', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(disabledStatus)
|
||||
await renderPage()
|
||||
expect(screen.getByText('Enable CrowdSec to manage banned IPs')).toBeInTheDocument()
|
||||
|
||||
cleanup()
|
||||
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(baseStatus)
|
||||
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockReturnValue(new Promise(() => {}))
|
||||
await renderPage()
|
||||
expect(screen.getByText('Loading banned IPs...')).toBeInTheDocument()
|
||||
|
||||
cleanup()
|
||||
|
||||
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockRejectedValueOnce(new Error('decisions'))
|
||||
await renderPage()
|
||||
expect(await screen.findByText('Failed to load banned IPs')).toBeInTheDocument()
|
||||
|
||||
cleanup()
|
||||
|
||||
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockResolvedValueOnce({ decisions: [] })
|
||||
await renderPage()
|
||||
expect(await screen.findByText('No banned IPs')).toBeInTheDocument()
|
||||
|
||||
cleanup()
|
||||
|
||||
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockResolvedValueOnce({
|
||||
decisions: [
|
||||
{ id: '1', ip: '1.1.1.1', reason: 'bot', duration: '24h', created_at: '2024-01-01T00:00:00Z', source: 'manual' },
|
||||
],
|
||||
})
|
||||
await renderPage()
|
||||
expect(await screen.findByText('1.1.1.1')).toBeInTheDocument()
|
||||
|
||||
vi.mocked(crowdsecApi.unbanIP).mockRejectedValueOnce(new Error('unban fail'))
|
||||
await userEvent.click(screen.getAllByText('Unban')[0])
|
||||
const confirmModal = screen.getByText('Confirm Unban').closest('div') as HTMLElement
|
||||
await userEvent.click(within(confirmModal).getByRole('button', { name: 'Unban' }))
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('unban fail'))
|
||||
})
|
||||
|
||||
it('bans and unbans IPs with overlay messaging', async () => {
|
||||
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockResolvedValue({
|
||||
decisions: [
|
||||
{ id: '1', ip: '1.1.1.1', reason: 'bot', duration: '24h', created_at: '2024-01-01T00:00:00Z', source: 'manual' },
|
||||
],
|
||||
})
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getByRole('button', { name: /Ban IP/ }))
|
||||
const banModal = screen.getByText('Ban IP Address').closest('div') as HTMLElement
|
||||
const ipInput = within(banModal).getByPlaceholderText('192.168.1.100') as HTMLInputElement
|
||||
await userEvent.type(ipInput, '2.2.2.2')
|
||||
await userEvent.click(within(banModal).getByRole('button', { name: 'Ban IP' }))
|
||||
await waitFor(() => expect(crowdsecApi.banIP).toHaveBeenCalledWith('2.2.2.2', '24h', ''))
|
||||
|
||||
// keep ban pending to assert overlay message
|
||||
let resolveBan: (() => void) | undefined
|
||||
vi.mocked(crowdsecApi.banIP).mockImplementationOnce(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
resolveBan = () => resolve()
|
||||
}),
|
||||
)
|
||||
await userEvent.click(screen.getByRole('button', { name: /Ban IP/ }))
|
||||
const banModalSecond = screen.getByText('Ban IP Address').closest('div') as HTMLElement
|
||||
await userEvent.type(within(banModalSecond).getByPlaceholderText('192.168.1.100'), '3.3.3.3')
|
||||
await userEvent.click(within(banModalSecond).getByRole('button', { name: 'Ban IP' }))
|
||||
expect(await screen.findByText('Guardian raises shield...')).toBeInTheDocument()
|
||||
resolveBan?.()
|
||||
|
||||
vi.mocked(crowdsecApi.unbanIP).mockImplementationOnce(() => new Promise(() => {}))
|
||||
const unbanButtons = await screen.findAllByText('Unban')
|
||||
await userEvent.click(unbanButtons[0])
|
||||
const confirmDialog = screen.getByText('Confirm Unban').closest('div') as HTMLElement
|
||||
await userEvent.click(within(confirmDialog).getByRole('button', { name: 'Unban' }))
|
||||
expect(await screen.findByText('Guardian lowers shield...')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows overlay messaging for preset pull, apply, import, write, and mode updates', async () => {
|
||||
// pull pending
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockImplementation(() => new Promise(() => {}))
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getByText('Pull Preview'))
|
||||
expect(await screen.findByText('Fetching preset...')).toBeInTheDocument()
|
||||
|
||||
cleanup()
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockReset()
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockResolvedValue({
|
||||
status: 'pulled',
|
||||
slug: presetFromCatalog.slug,
|
||||
preview: presetFromCatalog.content,
|
||||
cache_key: 'cache-123',
|
||||
})
|
||||
|
||||
// apply pending
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockResolvedValueOnce({
|
||||
status: 'pulled',
|
||||
slug: presetFromCatalog.slug,
|
||||
preview: presetFromCatalog.content,
|
||||
cache_key: 'cache-123',
|
||||
})
|
||||
let resolveApply: (() => void) | undefined
|
||||
vi.mocked(presetsApi.applyCrowdsecPreset).mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
resolveApply = () => resolve({ status: 'applied', cache_key: 'cache-123' } as never)
|
||||
}),
|
||||
)
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getAllByTestId('apply-preset-btn')[0])
|
||||
expect(await screen.findByText('Loading preset...')).toBeInTheDocument()
|
||||
resolveApply?.()
|
||||
|
||||
cleanup()
|
||||
|
||||
// import pending
|
||||
vi.mocked(presetsApi.pullCrowdsecPreset).mockResolvedValueOnce({
|
||||
status: 'pulled',
|
||||
slug: presetFromCatalog.slug,
|
||||
preview: presetFromCatalog.content,
|
||||
cache_key: 'cache-123',
|
||||
})
|
||||
let resolveImport: (() => void) | undefined
|
||||
vi.mocked(crowdsecApi.importCrowdsecConfig).mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
resolveImport = () => resolve({})
|
||||
}),
|
||||
)
|
||||
const { queryClient } = await renderPage(createTestQueryClient())
|
||||
await waitFor(() => expect(screen.getByTestId('preset-preview')).toBeInTheDocument())
|
||||
const fileInput = screen.getByTestId('import-file') as HTMLInputElement
|
||||
await userEvent.upload(fileInput, new File(['data'], 'cfg.tar.gz'))
|
||||
await userEvent.click(screen.getByTestId('import-btn'))
|
||||
expect(await screen.findByText('Summoning the guardian...')).toBeInTheDocument()
|
||||
resolveImport?.()
|
||||
await act(async () => queryClient.cancelQueries())
|
||||
|
||||
cleanup()
|
||||
|
||||
// write pending
|
||||
let resolveWrite: (() => void) | undefined
|
||||
vi.mocked(crowdsecApi.writeCrowdsecFile).mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
resolveWrite = () => resolve({})
|
||||
}),
|
||||
)
|
||||
await renderPage()
|
||||
await waitFor(() => expect(screen.getByTestId('preset-preview')).toBeInTheDocument())
|
||||
await userEvent.selectOptions(screen.getByTestId('crowdsec-file-select'), 'acquis.yaml')
|
||||
const textarea = screen.getByRole('textbox') as HTMLTextAreaElement
|
||||
await userEvent.type(textarea, 'x')
|
||||
await userEvent.click(screen.getByText('Save'))
|
||||
expect(await screen.findByText('Guardian inscribes...')).toBeInTheDocument()
|
||||
resolveWrite?.()
|
||||
|
||||
cleanup()
|
||||
|
||||
// mode update pending
|
||||
vi.mocked(settingsApi.updateSetting).mockImplementationOnce(() => new Promise(() => {}))
|
||||
await renderPage()
|
||||
await userEvent.click(screen.getByTestId('crowdsec-mode-toggle'))
|
||||
expect(await screen.findByText('Three heads turn...')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user