Files
headlamp-sealed-secrets-plugin/src/components/SealingKeysView.test.tsx
T
DevContainer User 9d9bc5f22f fix: remove any types, dead code, unused exports; add comprehensive tests
- Fix handleRotate bug ignoring Result from rotateSealedSecret()
- Fix dead code branch in useControllerHealth
- Replace all `any` types with `unknown` + type guards
- Delete unused functions/exports (452 lines removed)
- Add 18 new test files covering all hooks, libs, and components
- 233 tests passing, zero tsc errors, zero lint issues

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:13:00 +00:00

257 lines
7.0 KiB
TypeScript

/**
* 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[] };
}) => (
<div data-testid="section-box">
<h2>{title}</h2>
<div data-testid="header-actions">
{headerProps?.actions?.map((action, i) => (
<div key={i}>{action}</div>
))}
</div>
{children}
</div>
),
SimpleTable: ({
data,
columns,
}: {
data: unknown[];
columns: Array<{ label: string; getter: (row: unknown) => React.ReactNode }>;
}) => (
<table data-testid="keys-table">
<thead>
<tr>
{columns.map((col, i) => (
<th key={i}>{col.label}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={i}>
{columns.map((col, j) => (
<td key={j}>{col.getter(row)}</td>
))}
</tr>
))}
</tbody>
</table>
),
StatusLabel: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
}));
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: () => <div data-testid="controller-status">Status</div>,
}));
vi.mock('./LoadingSkeletons', () => ({
SealingKeysListSkeleton: () => <div data-testid="skeleton">Loading...</div>,
}));
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(<SealingKeysView />);
expect(screen.getByTestId('skeleton')).toBeDefined();
});
it('should show empty message when no sealing keys found', () => {
mockUseList.mockReturnValue([[], null, false] as never);
render(<SealingKeysView />);
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(<SealingKeysView />);
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(<SealingKeysView />);
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(<SealingKeysView />);
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(<SealingKeysView />);
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(<SealingKeysView />);
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(<SealingKeysView />);
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(<SealingKeysView />);
expect(screen.getByTestId('controller-status')).toBeDefined();
});
});