Files
Charon/frontend/src/pages/__tests__/EncryptionManagement.test.tsx
T
2026-01-26 19:22:05 +00:00

267 lines
7.8 KiB
TypeScript

import { render, screen, waitFor } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { BrowserRouter } from 'react-router-dom'
import EncryptionManagement from '../EncryptionManagement'
import * as encryptionApi from '../../api/encryption'
import userEvent from '@testing-library/user-event'
// Mock the API module
vi.mock('../../api/encryption')
const mockEncryptionApi = encryptionApi as {
getEncryptionStatus: ReturnType<typeof vi.fn>
getRotationHistory: ReturnType<typeof vi.fn>
rotateEncryptionKey: ReturnType<typeof vi.fn>
validateKeyConfiguration: ReturnType<typeof vi.fn>
}
describe('EncryptionManagement', () => {
let queryClient: QueryClient
const mockStatus = {
current_version: 2,
next_key_configured: true,
legacy_key_count: 1,
providers_on_current_version: 5,
providers_on_older_versions: 2,
}
const mockHistory = [
{
id: 1,
uuid: 'test-uuid-1',
actor: 'admin',
action: 'encryption_key_rotated',
event_category: 'encryption',
details: JSON.stringify({ new_key_version: 2, duration: '5.2s' }),
created_at: '2026-01-03T10:00:00Z',
},
]
beforeEach(() => {
queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
})
// Setup default mocks
mockEncryptionApi.getEncryptionStatus.mockResolvedValue(mockStatus)
mockEncryptionApi.getRotationHistory.mockResolvedValue(mockHistory)
})
const renderComponent = () => {
return render(
<BrowserRouter>
<QueryClientProvider client={queryClient}>
<EncryptionManagement />
</QueryClientProvider>
</BrowserRouter>
)
}
it('renders page title and description', async () => {
renderComponent()
await waitFor(() => {
expect(screen.getByText('Encryption Key Management')).toBeInTheDocument()
expect(screen.getByText('Manage encryption keys and rotate DNS provider credentials')).toBeInTheDocument()
})
})
it('displays encryption status correctly', async () => {
renderComponent()
await waitFor(() => {
expect(screen.getAllByText(/Version 2/)[0]).toBeInTheDocument()
expect(screen.getByText('5')).toBeInTheDocument() // providers on current version
expect(screen.getByText('Using current key version')).toBeInTheDocument()
expect(screen.getByText('Configured')).toBeInTheDocument() // next key status
})
})
it('shows warning when providers on older versions exist', async () => {
renderComponent()
await waitFor(() => {
expect(screen.getByText('Providers Outdated')).toBeInTheDocument()
})
})
it('displays legacy key warning when legacy keys exist', async () => {
renderComponent()
await waitFor(() => {
expect(screen.getByText('Legacy Encryption Keys Detected')).toBeInTheDocument()
expect(screen.getByText(/1 legacy keys are configured/)).toBeInTheDocument()
})
})
it('enables rotation button when next key is configured', async () => {
renderComponent()
await waitFor(() => {
const rotateButton = screen.getByText('Rotate Encryption Key')
expect(rotateButton).toBeEnabled()
})
})
it('disables rotation button when next key is not configured', async () => {
mockEncryptionApi.getEncryptionStatus.mockResolvedValue({
...mockStatus,
next_key_configured: false,
})
renderComponent()
await waitFor(() => {
const rotateButton = screen.getByText('Rotate Encryption Key')
expect(rotateButton).toBeDisabled()
})
})
it('shows confirmation dialog when rotation is triggered', async () => {
const user = userEvent.setup()
renderComponent()
await waitFor(() => {
expect(screen.getByText('Rotate Encryption Key')).toBeInTheDocument()
})
const rotateButton = screen.getByText('Rotate Encryption Key')
await user.click(rotateButton)
await waitFor(() => {
expect(screen.getByText('Confirm Key Rotation')).toBeInTheDocument()
expect(screen.getByText(/This will re-encrypt all DNS provider credentials/)).toBeInTheDocument()
})
})
it('executes rotation when confirmed', async () => {
const user = userEvent.setup()
const mockResult = {
total_providers: 7,
success_count: 7,
failure_count: 0,
duration: '5.2s',
new_key_version: 3,
}
mockEncryptionApi.rotateEncryptionKey.mockResolvedValue(mockResult)
renderComponent()
await waitFor(() => {
expect(screen.getByText('Rotate Encryption Key')).toBeInTheDocument()
})
// Open dialog
const rotateButton = screen.getByText('Rotate Encryption Key')
await user.click(rotateButton)
// Confirm rotation
await waitFor(() => {
expect(screen.getByText('Start Rotation')).toBeInTheDocument()
})
const confirmButton = screen.getByText('Start Rotation')
await user.click(confirmButton)
await waitFor(() => {
expect(mockEncryptionApi.rotateEncryptionKey).toHaveBeenCalled()
})
})
it('handles rotation errors gracefully', async () => {
const user = userEvent.setup()
mockEncryptionApi.rotateEncryptionKey.mockRejectedValue(new Error('Rotation failed'))
renderComponent()
await waitFor(() => {
expect(screen.getByText('Rotate Encryption Key')).toBeInTheDocument()
})
const rotateButton = screen.getByText('Rotate Encryption Key')
await user.click(rotateButton)
await waitFor(() => {
expect(screen.getByText('Start Rotation')).toBeInTheDocument()
})
const confirmButton = screen.getByText('Start Rotation')
await user.click(confirmButton)
await waitFor(() => {
expect(mockEncryptionApi.rotateEncryptionKey).toHaveBeenCalled()
})
})
it('validates key configuration when validate button is clicked', async () => {
const user = userEvent.setup()
const mockValidation = {
valid: true,
warnings: ['Keep old keys for 30 days'],
}
mockEncryptionApi.validateKeyConfiguration.mockResolvedValue(mockValidation)
renderComponent()
await waitFor(() => {
expect(screen.getByText('Validate Configuration')).toBeInTheDocument()
})
const validateButton = screen.getByText('Validate Configuration')
await user.click(validateButton)
await waitFor(() => {
expect(mockEncryptionApi.validateKeyConfiguration).toHaveBeenCalled()
})
})
it('displays rotation history', async () => {
renderComponent()
await waitFor(() => {
expect(screen.getByText('Rotation History')).toBeInTheDocument()
expect(screen.getByText('admin')).toBeInTheDocument()
expect(screen.getByText('encryption_key_rotated')).toBeInTheDocument()
})
})
it('displays environment variable guide', async () => {
renderComponent()
await waitFor(() => {
expect(screen.getByText('Environment Variable Configuration')).toBeInTheDocument()
expect(screen.getByText(/CHARON_ENCRYPTION_KEY=/)).toBeInTheDocument()
expect(screen.getByText(/CHARON_ENCRYPTION_KEY_V2=/)).toBeInTheDocument()
})
})
it('shows loading state while fetching status', () => {
mockEncryptionApi.getEncryptionStatus.mockImplementation(
() => new Promise(() => {}) // Never resolves
)
renderComponent()
expect(screen.getByText('Encryption Key Management')).toBeInTheDocument()
// Should show skeletons
expect(document.querySelectorAll('.animate-pulse').length).toBeGreaterThan(0)
})
it('shows error state when status fetch fails', async () => {
mockEncryptionApi.getEncryptionStatus.mockRejectedValue(new Error('Failed to fetch'))
renderComponent()
await waitFor(() => {
expect(screen.getByText('Failed to load encryption status. Please refresh the page.')).toBeInTheDocument()
})
})
})