Files
Charon/frontend/src/hooks/__tests__/useSecurity.test.tsx

299 lines
11 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { renderHook, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
useSecurityStatus,
useSecurityConfig,
useUpdateSecurityConfig,
useGenerateBreakGlassToken,
useDecisions,
useCreateDecision,
useRuleSets,
useUpsertRuleSet,
useDeleteRuleSet,
useEnableCerberus,
useDisableCerberus,
} from '../useSecurity'
import * as securityApi from '../../api/security'
import toast from 'react-hot-toast'
vi.mock('../../api/security')
vi.mock('react-hot-toast')
describe('useSecurity hooks', () => {
let queryClient: QueryClient
beforeEach(() => {
queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
})
vi.clearAllMocks()
})
const wrapper = ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
describe('useSecurityStatus', () => {
it('should fetch security status', async () => {
const mockStatus = {
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.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockStatus)
const { result } = renderHook(() => useSecurityStatus(), { wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data).toEqual(mockStatus)
})
})
describe('useSecurityConfig', () => {
it('should fetch security config', async () => {
const mockConfig = { config: { admin_whitelist: '10.0.0.0/8' } }
vi.mocked(securityApi.getSecurityConfig).mockResolvedValue(mockConfig)
const { result } = renderHook(() => useSecurityConfig(), { wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data).toEqual(mockConfig)
})
})
describe('useUpdateSecurityConfig', () => {
it('should update security config and invalidate queries on success', async () => {
const payload = { admin_whitelist: '192.168.0.0/16' }
vi.mocked(securityApi.updateSecurityConfig).mockResolvedValue({ success: true })
const { result } = renderHook(() => useUpdateSecurityConfig(), { wrapper })
result.current.mutate(payload)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.updateSecurityConfig).toHaveBeenCalledWith(payload)
expect(toast.success).toHaveBeenCalledWith('Security configuration updated')
})
it('should show error toast on failure', async () => {
const error = new Error('Update failed')
vi.mocked(securityApi.updateSecurityConfig).mockRejectedValue(error)
const { result } = renderHook(() => useUpdateSecurityConfig(), { wrapper })
result.current.mutate({ admin_whitelist: 'invalid' })
await waitFor(() => expect(result.current.isError).toBe(true))
expect(toast.error).toHaveBeenCalledWith('Failed to update security settings: Update failed')
})
})
describe('useGenerateBreakGlassToken', () => {
it('should generate break glass token', async () => {
const mockToken = { token: 'abc123' }
vi.mocked(securityApi.generateBreakGlassToken).mockResolvedValue(mockToken)
const { result } = renderHook(() => useGenerateBreakGlassToken(), { wrapper })
result.current.mutate(undefined)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data).toEqual(mockToken)
})
})
describe('useDecisions', () => {
it('should fetch decisions with default limit', async () => {
const mockDecisions = { decisions: [{ ip: '1.2.3.4', type: 'ban' }] }
vi.mocked(securityApi.getDecisions).mockResolvedValue(mockDecisions)
const { result } = renderHook(() => useDecisions(), { wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.getDecisions).toHaveBeenCalledWith(50)
expect(result.current.data).toEqual(mockDecisions)
})
it('should fetch decisions with custom limit', async () => {
const mockDecisions = { decisions: [] }
vi.mocked(securityApi.getDecisions).mockResolvedValue(mockDecisions)
const { result } = renderHook(() => useDecisions(100), { wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.getDecisions).toHaveBeenCalledWith(100)
})
})
describe('useCreateDecision', () => {
it('should create decision and invalidate queries', async () => {
const payload = { value: '1.2.3.4', duration: '4h', type: 'ban' }
vi.mocked(securityApi.createDecision).mockResolvedValue({ success: true })
const { result } = renderHook(() => useCreateDecision(), { wrapper })
result.current.mutate(payload)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.createDecision).toHaveBeenCalledWith(payload)
})
})
describe('useRuleSets', () => {
it('should fetch rule sets', async () => {
const mockRuleSets = {
rulesets: [{
id: 1,
uuid: 'abc-123',
name: 'OWASP CRS',
source_url: 'https://example.com',
mode: 'blocking',
last_updated: '2025-12-04',
content: 'rules'
}]
}
vi.mocked(securityApi.getRuleSets).mockResolvedValue(mockRuleSets)
const { result } = renderHook(() => useRuleSets(), { wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data).toEqual(mockRuleSets)
})
})
describe('useUpsertRuleSet', () => {
it('should upsert rule set and show success toast', async () => {
const payload = { name: 'Custom Rules', content: 'rule data', mode: 'blocking' as const }
vi.mocked(securityApi.upsertRuleSet).mockResolvedValue({ success: true })
const { result } = renderHook(() => useUpsertRuleSet(), { wrapper })
result.current.mutate(payload)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.upsertRuleSet).toHaveBeenCalledWith(payload)
expect(toast.success).toHaveBeenCalledWith('Rule set saved successfully')
})
it('should show error toast on failure', async () => {
const error = new Error('Save failed')
vi.mocked(securityApi.upsertRuleSet).mockRejectedValue(error)
const { result } = renderHook(() => useUpsertRuleSet(), { wrapper })
result.current.mutate({ name: 'Test', content: 'data', mode: 'blocking' })
await waitFor(() => expect(result.current.isError).toBe(true))
expect(toast.error).toHaveBeenCalledWith('Failed to save rule set: Save failed')
})
})
describe('useDeleteRuleSet', () => {
it('should delete rule set and show success toast', async () => {
vi.mocked(securityApi.deleteRuleSet).mockResolvedValue({ success: true })
const { result } = renderHook(() => useDeleteRuleSet(), { wrapper })
result.current.mutate(1)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.deleteRuleSet).toHaveBeenCalledWith(1)
expect(toast.success).toHaveBeenCalledWith('Rule set deleted')
})
it('should show error toast on failure', async () => {
const error = new Error('Delete failed')
vi.mocked(securityApi.deleteRuleSet).mockRejectedValue(error)
const { result } = renderHook(() => useDeleteRuleSet(), { wrapper })
result.current.mutate(1)
await waitFor(() => expect(result.current.isError).toBe(true))
expect(toast.error).toHaveBeenCalledWith('Failed to delete rule set: Delete failed')
})
})
describe('useEnableCerberus', () => {
it('should enable Cerberus and show success toast', async () => {
vi.mocked(securityApi.enableCerberus).mockResolvedValue({ success: true })
const { result } = renderHook(() => useEnableCerberus(), { wrapper })
result.current.mutate(undefined)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.enableCerberus).toHaveBeenCalledWith(undefined)
expect(toast.success).toHaveBeenCalledWith('Cerberus enabled')
})
it('should enable Cerberus with payload', async () => {
const payload = { mode: 'full' }
vi.mocked(securityApi.enableCerberus).mockResolvedValue({ success: true })
const { result } = renderHook(() => useEnableCerberus(), { wrapper })
result.current.mutate(payload)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.enableCerberus).toHaveBeenCalledWith(payload)
})
it('should show error toast on failure', async () => {
const error = new Error('Enable failed')
vi.mocked(securityApi.enableCerberus).mockRejectedValue(error)
const { result } = renderHook(() => useEnableCerberus(), { wrapper })
result.current.mutate(undefined)
await waitFor(() => expect(result.current.isError).toBe(true))
expect(toast.error).toHaveBeenCalledWith('Failed to enable Cerberus: Enable failed')
})
})
describe('useDisableCerberus', () => {
it('should disable Cerberus and show success toast', async () => {
vi.mocked(securityApi.disableCerberus).mockResolvedValue({ success: true })
const { result } = renderHook(() => useDisableCerberus(), { wrapper })
result.current.mutate(undefined)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.disableCerberus).toHaveBeenCalledWith(undefined)
expect(toast.success).toHaveBeenCalledWith('Cerberus disabled')
})
it('should disable Cerberus with payload', async () => {
const payload = { reason: 'maintenance' }
vi.mocked(securityApi.disableCerberus).mockResolvedValue({ success: true })
const { result } = renderHook(() => useDisableCerberus(), { wrapper })
result.current.mutate(payload)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(securityApi.disableCerberus).toHaveBeenCalledWith(payload)
})
it('should show error toast on failure', async () => {
const error = new Error('Disable failed')
vi.mocked(securityApi.disableCerberus).mockRejectedValue(error)
const { result } = renderHook(() => useDisableCerberus(), { wrapper })
result.current.mutate(undefined)
await waitFor(() => expect(result.current.isError).toBe(true))
expect(toast.error).toHaveBeenCalledWith('Failed to disable Cerberus: Disable failed')
})
})
})