refactor: remove Cerberus toggle from Security page and move feature flags to System Settings
- Removed the Cerberus toggle functionality from the Security page. - Introduced a new feature flags section in the System Settings page to manage Cerberus and Uptime Monitoring features. - Updated tests to reflect the changes in the Security and System Settings components. - Added loading overlays for feature toggling actions.
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* for the Security Dashboard implementation.
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
@@ -15,6 +15,14 @@ import * as crowdsecApi from '../../api/crowdsec'
|
||||
import * as settingsApi from '../../api/settings'
|
||||
import { toast } from '../../utils/toast'
|
||||
|
||||
const mockSecurityStatus = {
|
||||
cerberus: { enabled: true },
|
||||
crowdsec: { mode: 'local' as const, api_url: 'http://localhost', enabled: true },
|
||||
waf: { mode: 'enabled' as const, enabled: true },
|
||||
rate_limit: { enabled: true },
|
||||
acl: { enabled: true },
|
||||
}
|
||||
|
||||
vi.mock('../../api/security')
|
||||
vi.mock('../../api/crowdsec')
|
||||
vi.mock('../../api/settings')
|
||||
@@ -46,6 +54,10 @@ describe('Security Page - QA Security Audit', () => {
|
||||
},
|
||||
})
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: false })
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
vi.mocked(crowdsecApi.exportCrowdsecConfig).mockResolvedValue(new Blob())
|
||||
})
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
@@ -54,12 +66,10 @@ describe('Security Page - QA Security Audit', () => {
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
const mockSecurityStatus = {
|
||||
cerberus: { enabled: true },
|
||||
crowdsec: { mode: 'local' as const, api_url: 'http://localhost', enabled: true },
|
||||
waf: { mode: 'enabled' as const, enabled: true },
|
||||
rate_limit: { enabled: true },
|
||||
acl: { enabled: true }
|
||||
const renderSecurityPage = async () => {
|
||||
await act(async () => {
|
||||
render(<Security />, { wrapper })
|
||||
})
|
||||
}
|
||||
|
||||
describe('Input Validation', () => {
|
||||
@@ -68,7 +78,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
// won't execute. This test verifies that property.
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -82,7 +92,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
it('handles empty admin whitelist gracefully', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -98,7 +108,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(settingsApi.updateSetting).mockRejectedValue(new Error('Network error'))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-crowdsec'))
|
||||
const toggle = screen.getByTestId('toggle-crowdsec')
|
||||
@@ -115,7 +125,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: false })
|
||||
vi.mocked(crowdsecApi.startCrowdsec).mockRejectedValue(new Error('Failed to start'))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('crowdsec-start'))
|
||||
const startButton = screen.getByTestId('crowdsec-start')
|
||||
@@ -132,7 +142,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: true, pid: 1234 })
|
||||
vi.mocked(crowdsecApi.stopCrowdsec).mockRejectedValue(new Error('Failed to stop'))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('crowdsec-stop'))
|
||||
const stopButton = screen.getByTestId('crowdsec-stop')
|
||||
@@ -148,7 +158,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(crowdsecApi.exportCrowdsecConfig).mockRejectedValue(new Error('Export failed'))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByRole('button', { name: /Export/i }))
|
||||
const exportButton = screen.getByRole('button', { name: /Export/i })
|
||||
@@ -163,7 +173,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockRejectedValue(new Error('Status check failed'))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
// Page should still render even if status check fails
|
||||
await waitFor(() => expect(screen.getByText(/Security Dashboard/i)).toBeInTheDocument())
|
||||
@@ -177,14 +187,14 @@ describe('Security Page - QA Security Audit', () => {
|
||||
// Never resolving promise to simulate pending state
|
||||
vi.mocked(settingsApi.updateSetting).mockImplementation(() => new Promise(() => {}))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-cerberus'))
|
||||
const toggle = screen.getByTestId('toggle-cerberus')
|
||||
await waitFor(() => screen.getByTestId('toggle-waf'))
|
||||
const toggle = screen.getByTestId('toggle-waf')
|
||||
await user.click(toggle)
|
||||
|
||||
// Overlay should appear indicating operation in progress
|
||||
await waitFor(() => expect(screen.getByText(/Cerberus awakens/i)).toBeInTheDocument())
|
||||
await waitFor(() => expect(screen.getByText(/Three heads turn/i)).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('prevents double-click on CrowdSec start button', async () => {
|
||||
@@ -198,7 +208,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
return { success: true }
|
||||
})
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('crowdsec-start'))
|
||||
const startButton = screen.getByTestId('crowdsec-start')
|
||||
@@ -208,7 +218,9 @@ describe('Security Page - QA Security Audit', () => {
|
||||
await user.click(startButton)
|
||||
|
||||
// Wait for potential multiple calls
|
||||
await new Promise(resolve => setTimeout(resolve, 150))
|
||||
await act(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 150))
|
||||
})
|
||||
|
||||
// Should only be called once due to disabled state
|
||||
expect(callCount).toBe(1)
|
||||
@@ -221,7 +233,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -246,7 +258,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
it('shows correct layer indicator icons', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -267,7 +279,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
}
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(disabledStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -283,11 +295,10 @@ describe('Security Page - QA Security Audit', () => {
|
||||
it('all toggles have proper test IDs for automation', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
expect(screen.getByTestId('toggle-cerberus')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('toggle-crowdsec')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('toggle-acl')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('toggle-waf')).toBeInTheDocument()
|
||||
@@ -297,7 +308,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
it('WAF controls have proper test IDs when enabled', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -309,7 +320,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: false })
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -322,7 +333,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
it('pipeline order matches spec: CrowdSec → ACL → WAF → Rate Limiting', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -336,7 +347,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
it('layer indicators match spec descriptions', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -350,7 +361,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
it('threat summaries match spec when services enabled', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
@@ -374,7 +385,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
() => new Promise(resolve => setTimeout(resolve, 50))
|
||||
)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-waf'))
|
||||
|
||||
@@ -393,7 +404,7 @@ describe('Security Page - QA Security Audit', () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue(null as never)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
// Should not crash
|
||||
await waitFor(() => expect(screen.getByText(/Security Dashboard/i)).toBeInTheDocument())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
@@ -46,6 +46,12 @@ describe('Security', () => {
|
||||
},
|
||||
})
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: false })
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
vi.mocked(crowdsecApi.exportCrowdsecConfig).mockResolvedValue(new Blob())
|
||||
vi.spyOn(window, 'open').mockImplementation(() => null)
|
||||
vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
||||
})
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
@@ -54,6 +60,12 @@ describe('Security', () => {
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
const renderSecurityPage = async () => {
|
||||
await act(async () => {
|
||||
render(<Security />, { wrapper })
|
||||
})
|
||||
}
|
||||
|
||||
const mockSecurityStatus = {
|
||||
cerberus: { enabled: true },
|
||||
crowdsec: { mode: 'local' as const, api_url: 'http://localhost', enabled: true },
|
||||
@@ -63,68 +75,40 @@ describe('Security', () => {
|
||||
}
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should show loading state initially', () => {
|
||||
it('should show loading state initially', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockReturnValue(new Promise(() => {}))
|
||||
render(<Security />, { wrapper })
|
||||
|
||||
await renderSecurityPage()
|
||||
|
||||
expect(screen.getByText(/Loading security status/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show error if security status fails to load', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockRejectedValue(new Error('Failed'))
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
await waitFor(() => expect(screen.getByText(/Failed to load security status/i)).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('should render Security Dashboard when status loads', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
await waitFor(() => expect(screen.getByText(/Security Dashboard/i)).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('should show banner when Cerberus is disabled', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue({ ...mockSecurityStatus, cerberus: { enabled: false } })
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
await waitFor(() => expect(screen.getByText(/Security Suite Disabled/i)).toBeInTheDocument())
|
||||
})
|
||||
})
|
||||
|
||||
describe('Cerberus Toggle', () => {
|
||||
it('should toggle Cerberus on', async () => {
|
||||
const user = userEvent.setup()
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue({ ...mockSecurityStatus, cerberus: { enabled: false } })
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-cerberus'))
|
||||
const toggle = screen.getByTestId('toggle-cerberus')
|
||||
await user.click(toggle)
|
||||
|
||||
await waitFor(() => expect(settingsApi.updateSetting).toHaveBeenCalledWith('security.cerberus.enabled', 'true', 'security', 'bool'))
|
||||
})
|
||||
|
||||
it('should toggle Cerberus off', async () => {
|
||||
const user = userEvent.setup()
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-cerberus'))
|
||||
const toggle = screen.getByTestId('toggle-cerberus')
|
||||
await user.click(toggle)
|
||||
|
||||
await waitFor(() => expect(settingsApi.updateSetting).toHaveBeenCalledWith('security.cerberus.enabled', 'false', 'security', 'bool'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Service Toggles', () => {
|
||||
it('should toggle CrowdSec on', async () => {
|
||||
const user = userEvent.setup()
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue({ ...mockSecurityStatus, crowdsec: { mode: 'local', api_url: 'http://localhost', enabled: false } })
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-crowdsec'))
|
||||
const toggle = screen.getByTestId('toggle-crowdsec')
|
||||
@@ -138,11 +122,13 @@ describe('Security', () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue({ ...mockSecurityStatus, waf: { mode: 'enabled', enabled: false } })
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-waf'))
|
||||
const toggle = screen.getByTestId('toggle-waf')
|
||||
await user.click(toggle)
|
||||
await act(async () => {
|
||||
await user.click(toggle)
|
||||
})
|
||||
|
||||
await waitFor(() => expect(settingsApi.updateSetting).toHaveBeenCalledWith('security.waf.enabled', 'true', 'security', 'bool'))
|
||||
})
|
||||
@@ -152,7 +138,7 @@ describe('Security', () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue({ ...mockSecurityStatus, acl: { enabled: false } })
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-acl'))
|
||||
const toggle = screen.getByTestId('toggle-acl')
|
||||
@@ -166,7 +152,7 @@ describe('Security', () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue({ ...mockSecurityStatus, rate_limit: { enabled: false } })
|
||||
vi.mocked(settingsApi.updateSetting).mockResolvedValue()
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-rate-limit'))
|
||||
const toggle = screen.getByTestId('toggle-rate-limit')
|
||||
@@ -179,8 +165,8 @@ describe('Security', () => {
|
||||
describe('Admin Whitelist', () => {
|
||||
it('should load admin whitelist from config', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
render(<Security />, { wrapper })
|
||||
|
||||
await renderSecurityPage()
|
||||
await waitFor(() => screen.getByDisplayValue('10.0.0.0/8'))
|
||||
expect(screen.getByDisplayValue('10.0.0.0/8')).toBeInTheDocument()
|
||||
})
|
||||
@@ -192,7 +178,7 @@ describe('Security', () => {
|
||||
vi.mocked(useUpdateSecurityConfig).mockReturnValue({ mutate: mockMutate, isPending: false } as unknown as ReturnType<typeof useUpdateSecurityConfig>)
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByDisplayValue('10.0.0.0/8'))
|
||||
|
||||
@@ -212,11 +198,13 @@ describe('Security', () => {
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: false })
|
||||
vi.mocked(crowdsecApi.startCrowdsec).mockResolvedValue({ success: true })
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('crowdsec-start'))
|
||||
const startButton = screen.getByTestId('crowdsec-start')
|
||||
await user.click(startButton)
|
||||
await act(async () => {
|
||||
await user.click(startButton)
|
||||
})
|
||||
|
||||
await waitFor(() => expect(crowdsecApi.startCrowdsec).toHaveBeenCalled())
|
||||
})
|
||||
@@ -227,11 +215,13 @@ describe('Security', () => {
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: true, pid: 1234 })
|
||||
vi.mocked(crowdsecApi.stopCrowdsec).mockResolvedValue({ success: true })
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('crowdsec-stop'))
|
||||
const stopButton = screen.getByTestId('crowdsec-stop')
|
||||
await user.click(stopButton)
|
||||
await act(async () => {
|
||||
await user.click(stopButton)
|
||||
})
|
||||
|
||||
await waitFor(() => expect(crowdsecApi.stopCrowdsec).toHaveBeenCalled())
|
||||
})
|
||||
@@ -243,7 +233,7 @@ describe('Security', () => {
|
||||
window.URL.createObjectURL = vi.fn(() => 'blob:url')
|
||||
window.URL.revokeObjectURL = vi.fn()
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByRole('button', { name: /Export/i }))
|
||||
const exportButton = screen.getByRole('button', { name: /Export/i })
|
||||
@@ -264,7 +254,7 @@ describe('Security', () => {
|
||||
vi.mocked(useUpdateSecurityConfig).mockReturnValue({ mutate: mockMutate, isPending: false } as unknown as ReturnType<typeof useUpdateSecurityConfig>)
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('waf-mode-select'))
|
||||
const select = screen.getByTestId('waf-mode-select')
|
||||
@@ -280,7 +270,7 @@ describe('Security', () => {
|
||||
vi.mocked(useUpdateSecurityConfig).mockReturnValue({ mutate: mockMutate, isPending: false } as unknown as ReturnType<typeof useUpdateSecurityConfig>)
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('waf-ruleset-select'))
|
||||
const select = screen.getByTestId('waf-ruleset-select')
|
||||
@@ -293,8 +283,8 @@ describe('Security', () => {
|
||||
describe('Card Order (Pipeline Sequence)', () => {
|
||||
it('should render cards in correct pipeline order: CrowdSec → ACL → WAF → Rate Limiting', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
render(<Security />, { wrapper })
|
||||
|
||||
await renderSecurityPage()
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
// Get all card headings
|
||||
@@ -307,8 +297,8 @@ describe('Security', () => {
|
||||
|
||||
it('should display layer indicators on each card', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
render(<Security />, { wrapper })
|
||||
|
||||
await renderSecurityPage()
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
// Verify each layer indicator is present
|
||||
@@ -320,8 +310,8 @@ describe('Security', () => {
|
||||
|
||||
it('should display threat protection summaries', async () => {
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
render(<Security />, { wrapper })
|
||||
|
||||
await renderSecurityPage()
|
||||
await waitFor(() => screen.getByText(/Security Dashboard/i))
|
||||
|
||||
// Verify threat protection descriptions
|
||||
@@ -333,26 +323,12 @@ describe('Security', () => {
|
||||
})
|
||||
|
||||
describe('Loading Overlay', () => {
|
||||
it('should show Cerberus overlay when Cerberus is toggling', async () => {
|
||||
const user = userEvent.setup()
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(settingsApi.updateSetting).mockImplementation(() => new Promise(() => {}))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-cerberus'))
|
||||
const toggle = screen.getByTestId('toggle-cerberus')
|
||||
await user.click(toggle)
|
||||
|
||||
await waitFor(() => expect(screen.getByText(/Cerberus awakens/i)).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('should show overlay when service is toggling', async () => {
|
||||
const user = userEvent.setup()
|
||||
vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus)
|
||||
vi.mocked(settingsApi.updateSetting).mockImplementation(() => new Promise(() => {}))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('toggle-waf'))
|
||||
const toggle = screen.getByTestId('toggle-waf')
|
||||
@@ -367,7 +343,7 @@ describe('Security', () => {
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: false })
|
||||
vi.mocked(crowdsecApi.startCrowdsec).mockImplementation(() => new Promise(() => {}))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('crowdsec-start'))
|
||||
const startButton = screen.getByTestId('crowdsec-start')
|
||||
@@ -382,7 +358,7 @@ describe('Security', () => {
|
||||
vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: true, pid: 1234 })
|
||||
vi.mocked(crowdsecApi.stopCrowdsec).mockImplementation(() => new Promise(() => {}))
|
||||
|
||||
render(<Security />, { wrapper })
|
||||
await renderSecurityPage()
|
||||
|
||||
await waitFor(() => screen.getByTestId('crowdsec-stop'))
|
||||
const stopButton = screen.getByTestId('crowdsec-stop')
|
||||
|
||||
@@ -63,7 +63,10 @@ describe('SystemSettings', () => {
|
||||
'security.cerberus.enabled': 'false',
|
||||
})
|
||||
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({})
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
|
||||
'feature.cerberus.enabled': false,
|
||||
'feature.uptime.enabled': false,
|
||||
})
|
||||
|
||||
vi.mocked(client.get).mockResolvedValue({
|
||||
data: {
|
||||
@@ -382,44 +385,53 @@ describe('SystemSettings', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Optional Features', () => {
|
||||
it('renders the Optional Features section', async () => {
|
||||
describe('Features', () => {
|
||||
it('renders the Features section', async () => {
|
||||
renderWithProviders(<SystemSettings />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Optional Features')).toBeTruthy()
|
||||
expect(screen.getByText('Features')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
it('displays Cerberus Security Suite toggle', async () => {
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
|
||||
'feature.cerberus.enabled': true,
|
||||
'feature.uptime.enabled': false,
|
||||
})
|
||||
|
||||
renderWithProviders(<SystemSettings />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Cerberus Security Suite')).toBeTruthy()
|
||||
expect(screen.getByText('Advanced security features including WAF, Access Lists, Rate Limiting, and CrowdSec.')).toBeTruthy()
|
||||
})
|
||||
|
||||
const cerberusLabel = screen.getByText('Cerberus Security Suite')
|
||||
const tooltipParent = cerberusLabel.closest('[title]') as HTMLElement
|
||||
expect(tooltipParent?.getAttribute('title')).toContain('Advanced security features')
|
||||
})
|
||||
|
||||
it('displays Uptime Monitoring toggle', async () => {
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
|
||||
'feature.uptime.enabled': true,
|
||||
'feature.cerberus.enabled': false,
|
||||
})
|
||||
|
||||
renderWithProviders(<SystemSettings />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Uptime Monitoring')).toBeTruthy()
|
||||
expect(screen.getByText('Monitor the availability of your proxy hosts and remote servers.')).toBeTruthy()
|
||||
})
|
||||
|
||||
const uptimeLabel = screen.getByText('Uptime Monitoring')
|
||||
const tooltipParent = uptimeLabel.closest('[title]') as HTMLElement
|
||||
expect(tooltipParent?.getAttribute('title')).toContain('Monitor the availability')
|
||||
})
|
||||
|
||||
it('shows Cerberus toggle as checked when enabled', async () => {
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
|
||||
'feature.cerberus.enabled': true,
|
||||
'feature.uptime.enabled': false,
|
||||
})
|
||||
|
||||
renderWithProviders(<SystemSettings />)
|
||||
@@ -438,6 +450,7 @@ describe('SystemSettings', () => {
|
||||
it('shows Uptime toggle as checked when enabled', async () => {
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
|
||||
'feature.uptime.enabled': true,
|
||||
'feature.cerberus.enabled': false,
|
||||
})
|
||||
|
||||
renderWithProviders(<SystemSettings />)
|
||||
@@ -455,6 +468,7 @@ describe('SystemSettings', () => {
|
||||
it('shows Cerberus toggle as unchecked when disabled', async () => {
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
|
||||
'feature.cerberus.enabled': false,
|
||||
'feature.uptime.enabled': false,
|
||||
})
|
||||
|
||||
renderWithProviders(<SystemSettings />)
|
||||
@@ -472,6 +486,7 @@ describe('SystemSettings', () => {
|
||||
it('toggles Cerberus feature flag when switch is clicked', async () => {
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
|
||||
'feature.cerberus.enabled': false,
|
||||
'feature.uptime.enabled': false,
|
||||
})
|
||||
vi.mocked(featureFlagsApi.updateFeatureFlags).mockResolvedValue(undefined)
|
||||
|
||||
@@ -498,6 +513,7 @@ describe('SystemSettings', () => {
|
||||
it('toggles Uptime feature flag when switch is clicked', async () => {
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
|
||||
'feature.uptime.enabled': true,
|
||||
'feature.cerberus.enabled': false,
|
||||
})
|
||||
vi.mocked(featureFlagsApi.updateFeatureFlags).mockResolvedValue(undefined)
|
||||
|
||||
@@ -527,10 +543,37 @@ describe('SystemSettings', () => {
|
||||
renderWithProviders(<SystemSettings />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Optional Features')).toBeTruthy()
|
||||
expect(screen.getByText('Features')).toBeTruthy()
|
||||
})
|
||||
|
||||
expect(screen.getByText('Loading features...')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows loading overlay while toggling a feature flag', async () => {
|
||||
vi.mocked(featureFlagsApi.getFeatureFlags).mockResolvedValue({
|
||||
'feature.cerberus.enabled': false,
|
||||
'feature.uptime.enabled': false,
|
||||
})
|
||||
vi.mocked(featureFlagsApi.updateFeatureFlags).mockImplementation(
|
||||
() => new Promise(() => {})
|
||||
)
|
||||
|
||||
renderWithProviders(<SystemSettings />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Cerberus Security Suite')).toBeTruthy()
|
||||
})
|
||||
|
||||
const user = userEvent.setup()
|
||||
const cerberusText = screen.getByText('Cerberus Security Suite')
|
||||
const parentDiv = cerberusText.closest('.flex')
|
||||
const switchInput = parentDiv?.querySelector('input[type="checkbox"]') as HTMLInputElement
|
||||
|
||||
await user.click(switchInput)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Updating features...')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user