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();
});
});
});
});