import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import { describe, expect, it, vi } from 'vitest'; import { makeResult } from '../test-utils'; const { mockApiRequest } = vi.hoisted(() => ({ mockApiRequest: vi.fn() })); vi.mock('@kinvolk/headlamp-plugin/lib', () => ({ ApiProxy: { request: mockApiRequest }, })); vi.mock('@mui/material/styles', () => ({ useTheme: () => ({ palette: { primary: { main: '#1976d2', contrastText: '#fff' }, action: { disabledBackground: '#e0e0e0', disabled: '#9e9e9e' }, divider: '#e0e0e0', }, }), })); vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({ SectionBox: ({ title, children }: { title?: string; children?: React.ReactNode }) => (
{children}
), StatusLabel: ({ status, children }: { status: string; children?: React.ReactNode }) => ( {children} ), Dialog: ({ open, children, title, }: { open: boolean; onClose?: () => void; title?: string; children?: React.ReactNode; }) => open ? (
{children}
) : null, })); import ExemptionManager from './ExemptionManager'; const defaultProps = { workloadResult: makeResult(), namespace: 'default', kind: 'Deployment', name: 'my-deploy', }; const resultWithPodFailures = makeResult({ PodResult: { Name: 'pod', Results: { hostIPCSet: { ID: 'hostIPCSet', Message: 'Host IPC is set', Details: [], Success: false, Severity: 'danger', Category: 'Security', }, hostPIDSet: { ID: 'hostPIDSet', Message: 'Host PID is set', Details: [], Success: false, Severity: 'danger', Category: 'Security', }, }, ContainerResults: [], }, }); const resultWithContainerFailures = makeResult({ PodResult: { Name: 'pod', Results: {}, ContainerResults: [ { Name: 'container-1', Results: { cpuRequestsMissing: { ID: 'cpuRequestsMissing', Message: 'CPU requests missing', Details: [], Success: false, Severity: 'warning', Category: 'Efficiency', }, }, }, ], }, }); const resultWithIgnoredFailures = makeResult({ PodResult: { Name: 'pod', Results: { hostIPCSet: { ID: 'hostIPCSet', Message: '', Details: [], Success: false, Severity: 'ignore', Category: 'Security', }, }, ContainerResults: [], }, }); describe('ExemptionManager', () => { describe('rendering failing checks', () => { it('shows disabled Add Exemption button when no failing checks', () => { render(); const btn = screen.getByRole('button', { name: /add exemption/i }); expect(btn).toBeDisabled(); }); it('shows enabled Add Exemption button when there are failing checks', () => { render(); const btn = screen.getByRole('button', { name: /add exemption/i }); expect(btn).not.toBeDisabled(); }); it('does not include ignored-severity checks as failing', () => { render(); const btn = screen.getByRole('button', { name: /add exemption/i }); expect(btn).toBeDisabled(); }); it('collects failing checks from pod-level results', () => { render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); expect(screen.getByText('Host IPC')).toBeInTheDocument(); expect(screen.getByText('Host PID')).toBeInTheDocument(); }); it('collects failing checks from container-level results', () => { render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); expect(screen.getByText('CPU Requests')).toBeInTheDocument(); }); it('deduplicates checks that appear in multiple containers', () => { const resultWithDuplicate = makeResult({ PodResult: { Name: 'pod', Results: {}, ContainerResults: [ { Name: 'container-1', Results: { cpuRequestsMissing: { ID: 'cpuRequestsMissing', Message: '', Details: [], Success: false, Severity: 'warning', Category: 'Efficiency', }, }, }, { Name: 'container-2', Results: { cpuRequestsMissing: { ID: 'cpuRequestsMissing', Message: '', Details: [], Success: false, Severity: 'warning', Category: 'Efficiency', }, }, }, ], }, }); render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); const items = screen.getAllByText('CPU Requests'); expect(items).toHaveLength(1); }); }); describe('dialog interactions', () => { it('opens dialog when Add Exemption button is clicked', () => { render(); expect(screen.queryByTestId('dialog')).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); expect(screen.getByTestId('dialog')).toBeInTheDocument(); }); it('closes dialog when Cancel button is clicked', () => { render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); expect(screen.getByTestId('dialog')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: /cancel/i })); expect(screen.queryByTestId('dialog')).not.toBeInTheDocument(); }); it('toggles individual check selection', () => { render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); // Find the checkbox next to "Host IPC" const checkboxes = screen.getAllByRole('checkbox'); // First checkbox is "Exempt from all checks", rest are individual checks const hostIPCCheckbox = checkboxes[1]; expect(hostIPCCheckbox).not.toBeChecked(); fireEvent.click(hostIPCCheckbox); expect(hostIPCCheckbox).toBeChecked(); fireEvent.click(hostIPCCheckbox); expect(hostIPCCheckbox).not.toBeChecked(); }); it('hides individual checks list when exempt-all is toggled', () => { render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); expect(screen.getByText('Host IPC')).toBeInTheDocument(); const exemptAllCheckbox = screen.getByRole('checkbox', { name: /exempt from all checks/i }); fireEvent.click(exemptAllCheckbox); expect(screen.queryByText('Host IPC')).not.toBeInTheDocument(); }); it('Apply button is disabled when no checks selected and exemptAll is false', () => { render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); expect(screen.getByRole('button', { name: /apply/i })).toBeDisabled(); }); it('Apply button is enabled when exemptAll is checked', () => { render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); const exemptAllCheckbox = screen.getByRole('checkbox', { name: /exempt from all checks/i }); fireEvent.click(exemptAllCheckbox); expect(screen.getByRole('button', { name: /apply/i })).not.toBeDisabled(); }); it('Apply button is enabled when at least one individual check is selected', () => { render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); const checkboxes = screen.getAllByRole('checkbox'); fireEvent.click(checkboxes[1]); // select first individual check expect(screen.getByRole('button', { name: /apply/i })).not.toBeDisabled(); }); }); describe('ApiProxy.request calls', () => { it('patches with exempt-all annotation when exemptAll is selected', async () => { mockApiRequest.mockResolvedValue({}); render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i })); fireEvent.click(screen.getByRole('button', { name: /apply/i })); await waitFor(() => { expect(mockApiRequest).toHaveBeenCalledWith( '/apis/apps/v1/namespaces/default/deployments/my-deploy', expect.objectContaining({ method: 'PATCH', headers: { 'Content-Type': 'application/strategic-merge-patch+json' }, body: JSON.stringify({ metadata: { annotations: { 'polaris.fairwinds.com/exempt': 'true' }, }, }), }) ); }); }); it('patches with per-check annotations when individual checks selected', async () => { mockApiRequest.mockResolvedValue({}); render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); // Select first check (hostIPCSet) fireEvent.click(screen.getAllByRole('checkbox')[1]); fireEvent.click(screen.getByRole('button', { name: /apply/i })); await waitFor(() => { expect(mockApiRequest).toHaveBeenCalledWith( '/apis/apps/v1/namespaces/default/deployments/my-deploy', expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ metadata: { annotations: { 'polaris.fairwinds.com/hostIPCSet-exempt': 'true' }, }, }), }) ); }); }); it('uses core API path for Pod kind (no api group)', async () => { mockApiRequest.mockResolvedValue({}); render( ); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i })); fireEvent.click(screen.getByRole('button', { name: /apply/i })); await waitFor(() => { expect(mockApiRequest).toHaveBeenCalledWith( '/api/v1/namespaces/default/pods/my-deploy', expect.anything() ); }); }); it('uses batch API group for Job kind', async () => { mockApiRequest.mockResolvedValue({}); render( ); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i })); fireEvent.click(screen.getByRole('button', { name: /apply/i })); await waitFor(() => { expect(mockApiRequest).toHaveBeenCalledWith( '/apis/batch/v1/namespaces/default/jobs/my-deploy', expect.anything() ); }); }); it('uses batch API group for CronJob kind', async () => { mockApiRequest.mockResolvedValue({}); render( ); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i })); fireEvent.click(screen.getByRole('button', { name: /apply/i })); await waitFor(() => { expect(mockApiRequest).toHaveBeenCalledWith( '/apis/batch/v1/namespaces/default/cronjobs/my-deploy', expect.anything() ); }); }); it('uses apps API group for StatefulSet kind', async () => { mockApiRequest.mockResolvedValue({}); render( ); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i })); fireEvent.click(screen.getByRole('button', { name: /apply/i })); await waitFor(() => { expect(mockApiRequest).toHaveBeenCalledWith( '/apis/apps/v1/namespaces/default/statefulsets/my-deploy', expect.anything() ); }); }); }); describe('feedback states', () => { it('shows success feedback and closes dialog after successful apply', async () => { mockApiRequest.mockResolvedValue({}); render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i })); fireEvent.click(screen.getByRole('button', { name: /apply/i })); await waitFor(() => { expect(screen.queryByTestId('dialog')).not.toBeInTheDocument(); const label = screen.getByTestId('status-label'); expect(label).toHaveAttribute('data-status', 'success'); expect(label).toHaveTextContent('Exemptions applied successfully'); }); }); it('shows error feedback and keeps dialog closed after failed apply', async () => { mockApiRequest.mockRejectedValue(new Error('403 Forbidden')); render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i })); fireEvent.click(screen.getByRole('button', { name: /apply/i })); await waitFor(() => { const label = screen.getByTestId('status-label'); expect(label).toHaveAttribute('data-status', 'error'); expect(label).toHaveTextContent(/failed to apply exemptions/i); }); }); it('shows "Applying..." text on Apply button while in-flight', async () => { let resolveRequest!: () => void; mockApiRequest.mockReturnValue( new Promise(res => { resolveRequest = res; }) ); render(); fireEvent.click(screen.getByRole('button', { name: /add exemption/i })); fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i })); fireEvent.click(screen.getByRole('button', { name: /apply/i })); expect(screen.getByRole('button', { name: /applying/i })).toBeInTheDocument(); resolveRequest(); await waitFor(() => { expect(screen.queryByTestId('dialog')).not.toBeInTheDocument(); }); }); }); });