/** * Unit tests for SealingKeysView component */ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; // Mock notistack const mockEnqueueSnackbar = vi.fn(); vi.mock('notistack', () => ({ useSnackbar: () => ({ enqueueSnackbar: mockEnqueueSnackbar }), })); // Mock headlamp vi.mock('@kinvolk/headlamp-plugin/lib', () => ({ K8s: { ResourceClasses: { Secret: { useList: vi.fn(), }, }, }, })); vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({ SectionBox: ({ title, children, headerProps, }: { title: string; children: React.ReactNode; headerProps?: { actions?: React.ReactNode[] }; }) => (

{title}

{headerProps?.actions?.map((action, i) => (
{action}
))}
{children}
), SimpleTable: ({ data, columns, }: { data: unknown[]; columns: Array<{ label: string; getter: (row: unknown) => React.ReactNode }>; }) => ( {columns.map((col, i) => ( ))} {data.map((row, i) => ( {columns.map((col, j) => ( ))} ))}
{col.label}
{col.getter(row)}
), StatusLabel: ({ children }: { children: React.ReactNode }) => {children}, })); vi.mock('../lib/controller', () => ({ getPluginConfig: vi.fn().mockReturnValue({ controllerName: 'sealed-secrets-controller', controllerNamespace: 'kube-system', controllerPort: 8080, }), fetchPublicCertificate: vi.fn(), })); vi.mock('../lib/crypto', () => ({ parseCertificateInfo: vi.fn().mockReturnValue({ ok: false, error: 'no cert' }), isCertificateExpiringSoon: vi.fn().mockReturnValue(false), })); vi.mock('./ControllerStatus', () => ({ ControllerStatus: () =>
Status
, })); vi.mock('./LoadingSkeletons', () => ({ SealingKeysListSkeleton: () =>
Loading...
, })); import { K8s } from '@kinvolk/headlamp-plugin/lib'; import { fetchPublicCertificate } from '../lib/controller'; import { SealingKeysView } from './SealingKeysView'; const mockUseList = vi.mocked(K8s.ResourceClasses.Secret.useList); const mockFetchCert = vi.mocked(fetchPublicCertificate); describe('SealingKeysView', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should show loading skeleton', () => { mockUseList.mockReturnValue([null, null, true] as never); render(); expect(screen.getByTestId('skeleton')).toBeDefined(); }); it('should show empty message when no sealing keys found', () => { mockUseList.mockReturnValue([[], null, false] as never); render(); expect(screen.getByText(/No sealing keys found/)).toBeDefined(); }); it('should render sealing keys table', () => { const secrets = [ { metadata: { name: 'sealed-secrets-key-abc', labels: { 'sealedsecrets.bitnami.com/sealed-secrets-key': 'active' }, creationTimestamp: '2024-01-01T00:00:00Z', }, data: {}, }, { metadata: { name: 'sealed-secrets-key-old', labels: { 'sealedsecrets.bitnami.com/sealed-secrets-key': 'compromised' }, creationTimestamp: '2023-06-01T00:00:00Z', }, data: {}, }, ]; mockUseList.mockReturnValue([secrets, null, false] as never); render(); expect(screen.getByTestId('keys-table')).toBeDefined(); expect(screen.getByText('sealed-secrets-key-abc')).toBeDefined(); expect(screen.getByText('sealed-secrets-key-old')).toBeDefined(); }); it('should filter non-sealing-key secrets', () => { const secrets = [ { metadata: { name: 'sealing-key', labels: { 'sealedsecrets.bitnami.com/sealed-secrets-key': 'active' }, creationTimestamp: '2024-01-01T00:00:00Z', }, data: {}, }, { metadata: { name: 'other-secret', labels: {}, creationTimestamp: '2024-01-01T00:00:00Z', }, data: {}, }, ]; mockUseList.mockReturnValue([secrets, null, false] as never); render(); expect(screen.getByText('sealing-key')).toBeDefined(); expect(screen.queryByText('other-secret')).toBeNull(); }); it('should sort active keys before compromised', () => { const secrets = [ { metadata: { name: 'compromised-key', labels: { 'sealedsecrets.bitnami.com/sealed-secrets-key': 'compromised' }, creationTimestamp: '2024-06-01T00:00:00Z', }, data: {}, }, { metadata: { name: 'active-key', labels: { 'sealedsecrets.bitnami.com/sealed-secrets-key': 'active' }, creationTimestamp: '2024-01-01T00:00:00Z', }, data: {}, }, ]; mockUseList.mockReturnValue([secrets, null, false] as never); render(); const rows = screen.getAllByRole('row'); // First data row should be active key (after header row) expect(rows[1].textContent).toContain('active-key'); }); it('should show download certificate button', () => { mockUseList.mockReturnValue([[], null, false] as never); render(); expect(screen.getByText('Download Public Certificate')).toBeDefined(); }); it('should handle certificate download failure', async () => { mockUseList.mockReturnValue([[], null, false] as never); mockFetchCert.mockResolvedValue({ ok: false, error: 'Network error' }); render(); fireEvent.click(screen.getByText('Download Public Certificate')); await waitFor(() => { expect(mockEnqueueSnackbar).toHaveBeenCalledWith( expect.stringContaining('Failed to download certificate'), { variant: 'error' } ); }); }); it('should call fetchPublicCertificate on download click', async () => { mockUseList.mockReturnValue([[], null, false] as never); mockFetchCert.mockResolvedValue({ ok: true, value: 'cert-pem' as never }); // Mock Blob/URL to prevent DOM issues global.URL.createObjectURL = vi.fn().mockReturnValue('blob:url'); global.URL.revokeObjectURL = vi.fn(); render(); fireEvent.click(screen.getByText('Download Public Certificate')); await waitFor(() => { expect(mockFetchCert).toHaveBeenCalled(); }); }); it('should show ControllerStatus in header', () => { mockUseList.mockReturnValue([[], null, false] as never); render(); expect(screen.getByTestId('controller-status')).toBeDefined(); }); });