feat(tests): add CrowdSec Console Enrollment feature flag tests in SystemSettings and CrowdSecConfig

This commit is contained in:
GitHub Actions
2025-12-11 05:09:03 +00:00
parent 28ad90d962
commit 97c2ef9b71
2 changed files with 164 additions and 0 deletions

View File

@@ -10,6 +10,8 @@ import * as crowdsecApi from '../../api/crowdsec'
import * as backupsApi from '../../api/backups'
import * as settingsApi from '../../api/settings'
import * as presetsApi from '../../api/presets'
import * as featureFlagsApi from '../../api/featureFlags'
import * as consoleApi from '../../api/consoleEnrollment'
import { CROWDSEC_PRESETS } from '../../data/crowdsecPresets'
vi.mock('../../api/security')
@@ -17,6 +19,8 @@ vi.mock('../../api/crowdsec')
vi.mock('../../api/backups')
vi.mock('../../api/settings')
vi.mock('../../api/presets')
vi.mock('../../api/featureFlags')
vi.mock('../../api/consoleEnrollment')
const createQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })
const renderWithProviders = (ui: React.ReactNode) => {
@@ -63,6 +67,11 @@ describe('CrowdSecConfig', () => {
})
vi.mocked(presetsApi.getCrowdsecPresetCache).mockResolvedValue({ preview: 'cached', cache_key: 'cache-123', etag: 'etag-123' })
vi.mocked(crowdsecApi.listCrowdsecDecisions).mockResolvedValue({ decisions: [] })
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.crowdsec.console_enrollment': false,
})
vi.mocked(consoleApi.getConsoleStatus).mockResolvedValue({ status: 'not_enrolled', key_present: false })
vi.mocked(consoleApi.enrollConsole).mockResolvedValue({ status: 'enrolling', key_present: true })
})
it('exports config when clicking Export', async () => {
@@ -94,6 +103,103 @@ describe('CrowdSecConfig', () => {
await waitFor(() => expect(crowdsecApi.importCrowdsecConfig).toHaveBeenCalled())
})
it('hides console enrollment when feature flag is off', async () => {
vi.mocked(api.getSecurityStatus).mockResolvedValue({ crowdsec: { enabled: true, mode: 'local' as const, api_url: '' }, cerberus: { enabled: true }, waf: { enabled: false, mode: 'disabled' as const }, rate_limit: { enabled: false }, acl: { enabled: false } })
vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: [] })
renderWithProviders(<CrowdSecConfig />)
await waitFor(() => expect(screen.getByText('CrowdSec Configuration')).toBeInTheDocument())
expect(screen.queryByTestId('console-enrollment-card')).not.toBeInTheDocument()
})
it('shows console enrollment form when feature flag is on', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({ 'feature.crowdsec.console_enrollment': true })
vi.mocked(api.getSecurityStatus).mockResolvedValue({ crowdsec: { enabled: true, mode: 'local' as const, api_url: '' }, cerberus: { enabled: true }, waf: { enabled: false, mode: 'disabled' as const }, rate_limit: { enabled: false }, acl: { enabled: false } })
vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: [] })
renderWithProviders(<CrowdSecConfig />)
await waitFor(() => expect(screen.getByTestId('console-enrollment-card')).toBeInTheDocument())
expect(screen.getByTestId('console-enrollment-token')).toBeInTheDocument()
})
it('validates required console enrollment fields and acknowledgement', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({ 'feature.crowdsec.console_enrollment': true })
vi.mocked(api.getSecurityStatus).mockResolvedValue({ crowdsec: { enabled: true, mode: 'local' as const, api_url: '' }, cerberus: { enabled: true }, waf: { enabled: false, mode: 'disabled' as const }, rate_limit: { enabled: false }, acl: { enabled: false } })
vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: [] })
renderWithProviders(<CrowdSecConfig />)
const enrollBtn = await screen.findByTestId('console-enroll-btn')
await userEvent.click(enrollBtn)
const errors = await screen.findAllByTestId('console-enroll-error')
expect(errors.length).toBeGreaterThan(0)
expect(consoleApi.enrollConsole).not.toHaveBeenCalled()
})
it('submits console enrollment payload with snake_case fields', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({ 'feature.crowdsec.console_enrollment': true })
vi.mocked(api.getSecurityStatus).mockResolvedValue({ crowdsec: { enabled: true, mode: 'local' as const, api_url: '' }, cerberus: { enabled: true }, waf: { enabled: false, mode: 'disabled' as const }, rate_limit: { enabled: false }, acl: { enabled: false } })
vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: [] })
vi.mocked(consoleApi.enrollConsole).mockResolvedValue({ status: 'enrolled', key_present: true, agent_name: 'agent-one', tenant: 'tenant-inc' })
renderWithProviders(<CrowdSecConfig />)
await waitFor(() => expect(screen.getByTestId('console-enrollment-card')).toBeInTheDocument())
await userEvent.type(screen.getByTestId('console-enrollment-token'), 'secret-1234567890')
await userEvent.clear(screen.getByTestId('console-agent-name'))
await userEvent.type(screen.getByTestId('console-agent-name'), 'agent-one')
await userEvent.type(screen.getByTestId('console-tenant'), 'tenant-inc')
await userEvent.click(screen.getByTestId('console-ack-checkbox'))
await userEvent.click(screen.getByTestId('console-enroll-btn'))
await waitFor(() => expect(consoleApi.enrollConsole).toHaveBeenCalledWith({
enrollment_key: 'secret-1234567890',
agent_name: 'agent-one',
tenant: 'tenant-inc',
force: false,
}))
expect((screen.getByTestId('console-enrollment-token') as HTMLInputElement).value).toBe('')
})
it('renders masked key state in console status', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({ 'feature.crowdsec.console_enrollment': true })
vi.mocked(api.getSecurityStatus).mockResolvedValue({ crowdsec: { enabled: true, mode: 'local' as const, api_url: '' }, cerberus: { enabled: true }, waf: { enabled: false, mode: 'disabled' as const }, rate_limit: { enabled: false }, acl: { enabled: false } })
vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: [] })
vi.mocked(consoleApi.getConsoleStatus).mockResolvedValue({ status: 'enrolled', key_present: true, agent_name: 'a1', tenant: 't1', last_heartbeat_at: '2024-01-01T00:00:00Z' })
renderWithProviders(<CrowdSecConfig />)
await waitFor(() => expect(screen.getByTestId('console-token-state')).toHaveTextContent('Stored (masked)'))
})
it('retries degraded enrollment and rotates key when enrolled', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({ 'feature.crowdsec.console_enrollment': true })
vi.mocked(api.getSecurityStatus).mockResolvedValue({ crowdsec: { enabled: true, mode: 'local' as const, api_url: '' }, cerberus: { enabled: true }, waf: { enabled: false, mode: 'disabled' as const }, rate_limit: { enabled: false }, acl: { enabled: false } })
vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: [] })
vi.mocked(consoleApi.getConsoleStatus).mockResolvedValueOnce({ status: 'failed', key_present: true, last_error: 'network' })
vi.mocked(consoleApi.getConsoleStatus).mockResolvedValue({ status: 'enrolled', key_present: true })
renderWithProviders(<CrowdSecConfig />)
await waitFor(() => expect(screen.getByTestId('console-ack-checkbox')).toBeInTheDocument())
await userEvent.type(screen.getByTestId('console-enrollment-token'), 'another-secret-123456')
await userEvent.click(screen.getByTestId('console-ack-checkbox'))
await userEvent.click(screen.getByTestId('console-retry-btn'))
await waitFor(() => expect(consoleApi.enrollConsole).toHaveBeenCalledWith(expect.objectContaining({ force: true })))
await waitFor(() => expect(screen.getByTestId('console-rotate-btn')).not.toBeDisabled())
await userEvent.type(screen.getByTestId('console-enrollment-token'), 'rotate-token-987654321')
await userEvent.click(screen.getByTestId('console-rotate-btn'))
await waitFor(() => expect(consoleApi.enrollConsole).toHaveBeenCalledWith(expect.objectContaining({
enrollment_key: 'rotate-token-987654321',
force: true,
})))
})
it('lists files, reads file content and can save edits (backup before save)', async () => {
const status = { crowdsec: { enabled: true, mode: 'local' as const, api_url: '' }, cerberus: { enabled: true }, waf: { enabled: false, mode: 'disabled' as const }, rate_limit: { enabled: false }, acl: { enabled: false } }
vi.mocked(api.getSecurityStatus).mockResolvedValue(status)

View File

@@ -65,6 +65,7 @@ describe('SystemSettings', () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.cerberus.enabled': false,
'feature.crowdsec.console_enrollment': false,
'feature.uptime.enabled': false,
})
@@ -397,6 +398,7 @@ describe('SystemSettings', () => {
it('displays Cerberus Security Suite toggle', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.cerberus.enabled': true,
'feature.crowdsec.console_enrollment': false,
'feature.uptime.enabled': false,
})
@@ -411,10 +413,32 @@ describe('SystemSettings', () => {
expect(tooltipParent?.getAttribute('title')).toContain('Advanced security features')
})
it('displays CrowdSec Console Enrollment toggle', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.cerberus.enabled': false,
'feature.crowdsec.console_enrollment': true,
'feature.uptime.enabled': false,
})
renderWithProviders(<SystemSettings />)
await waitFor(() => {
expect(screen.getByText('CrowdSec Console Enrollment')).toBeTruthy()
})
const crowdsecLabel = screen.getByText('CrowdSec Console Enrollment')
const tooltipParent = crowdsecLabel.closest('[title]') as HTMLElement
expect(tooltipParent?.getAttribute('title')).toContain('CrowdSec Console')
const switchInput = tooltipParent?.querySelector('input[type="checkbox"]') as HTMLInputElement
expect(switchInput?.checked).toBe(true)
})
it('displays Uptime Monitoring toggle', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.uptime.enabled': true,
'feature.cerberus.enabled': false,
'feature.crowdsec.console_enrollment': false,
})
renderWithProviders(<SystemSettings />)
@@ -431,6 +455,7 @@ describe('SystemSettings', () => {
it('shows Cerberus toggle as checked when enabled', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.cerberus.enabled': true,
'feature.crowdsec.console_enrollment': false,
'feature.uptime.enabled': false,
})
@@ -451,6 +476,7 @@ describe('SystemSettings', () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.uptime.enabled': true,
'feature.cerberus.enabled': false,
'feature.crowdsec.console_enrollment': false,
})
renderWithProviders(<SystemSettings />)
@@ -468,6 +494,7 @@ describe('SystemSettings', () => {
it('shows Cerberus toggle as unchecked when disabled', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.cerberus.enabled': false,
'feature.crowdsec.console_enrollment': false,
'feature.uptime.enabled': false,
})
@@ -486,6 +513,7 @@ describe('SystemSettings', () => {
it('toggles Cerberus feature flag when switch is clicked', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.cerberus.enabled': false,
'feature.crowdsec.console_enrollment': false,
'feature.uptime.enabled': false,
})
vi.mocked(featureFlagsApi.updateFeatureFlags).mockResolvedValue(undefined)
@@ -510,10 +538,39 @@ describe('SystemSettings', () => {
})
})
it('toggles CrowdSec Console Enrollment feature flag when switch is clicked', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.cerberus.enabled': false,
'feature.crowdsec.console_enrollment': false,
'feature.uptime.enabled': false,
})
vi.mocked(featureFlagsApi.updateFeatureFlags).mockResolvedValue(undefined)
renderWithProviders(<SystemSettings />)
await waitFor(() => {
expect(screen.getByText('CrowdSec Console Enrollment')).toBeTruthy()
})
const user = userEvent.setup()
const crowdsecLabel = screen.getByText('CrowdSec Console Enrollment')
const parentDiv = crowdsecLabel.closest('.flex')
const switchInput = parentDiv?.querySelector('input[type="checkbox"]') as HTMLInputElement
await user.click(switchInput)
await waitFor(() => {
expect(featureFlagsApi.updateFeatureFlags).toHaveBeenCalledWith({
'feature.crowdsec.console_enrollment': true,
})
})
})
it('toggles Uptime feature flag when switch is clicked', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.uptime.enabled': true,
'feature.cerberus.enabled': false,
'feature.crowdsec.console_enrollment': false,
})
vi.mocked(featureFlagsApi.updateFeatureFlags).mockResolvedValue(undefined)
@@ -552,6 +609,7 @@ describe('SystemSettings', () => {
it('shows loading overlay while toggling a feature flag', async () => {
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
'feature.cerberus.enabled': false,
'feature.crowdsec.console_enrollment': false,
'feature.uptime.enabled': false,
})
vi.mocked(featureFlagsApi.updateFeatureFlags).mockImplementation(