c1c5e8a37d
- Fix PVC bind loop leak on unmount via cancellation ref - Fix DeleteOptions body structure for proper foreground propagation - Filter snapshots to tns-csi driver only (was showing all drivers) - Fix stale closures in Escape key handlers with useCallback - Add loading state to cleanup delete button, remove window.confirm/alert - Use CSS custom properties for protocol chart colors (dark mode support) - Fix all 35 ESLint warnings (import sort, indent, boolean attrs) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
163 lines
6.2 KiB
TypeScript
163 lines
6.2 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
vi.mock(
|
|
'@kinvolk/headlamp-plugin/lib/CommonComponents',
|
|
async () => await import('./__mocks__/commonComponents')
|
|
);
|
|
|
|
vi.mock('../api/TnsCsiDataContext');
|
|
vi.mock('../api/metrics', async importOriginal => {
|
|
const actual = await importOriginal<typeof import('../api/metrics')>();
|
|
return {
|
|
...actual,
|
|
fetchControllerMetrics: vi.fn(),
|
|
};
|
|
});
|
|
|
|
import { fetchControllerMetrics } from '../api/metrics';
|
|
import { useTnsCsiContext } from '../api/TnsCsiDataContext';
|
|
import { defaultContext, makeSampleMetrics, makeSamplePod } from '../test-helpers';
|
|
import MetricsPage from './MetricsPage';
|
|
|
|
function mockContext(overrides?: Parameters<typeof defaultContext>[0]) {
|
|
vi.mocked(useTnsCsiContext).mockReturnValue(defaultContext(overrides));
|
|
}
|
|
|
|
describe('MetricsPage', () => {
|
|
beforeEach(() => {
|
|
vi.mocked(fetchControllerMetrics).mockReset();
|
|
});
|
|
|
|
it('shows loader when context is loading', () => {
|
|
mockContext({ loading: true });
|
|
render(<MetricsPage />);
|
|
expect(screen.getByTestId('loader')).toHaveTextContent('Loading tns-csi data...');
|
|
});
|
|
|
|
it('shows "Driver Not Detected" when driver not installed', () => {
|
|
mockContext({ driverInstalled: false });
|
|
render(<MetricsPage />);
|
|
expect(screen.getByText('Driver Not Detected')).toBeInTheDocument();
|
|
expect(screen.getByText(/TNS-CSI driver not found/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows "No controller pod found" when driver installed but no pods', () => {
|
|
mockContext({ driverInstalled: true, controllerPods: [] });
|
|
render(<MetricsPage />);
|
|
expect(screen.getByText('Metrics Unavailable')).toBeInTheDocument();
|
|
expect(screen.getByText(/No controller pod found/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows metrics error when fetch fails', async () => {
|
|
const pod = makeSamplePod();
|
|
mockContext({ driverInstalled: true, controllerPods: [pod] });
|
|
vi.mocked(fetchControllerMetrics).mockRejectedValueOnce(new Error('connection refused'));
|
|
render(<MetricsPage />);
|
|
await waitFor(() => {
|
|
expect(screen.getByText('connection refused')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('renders three metric cards when fetch succeeds', async () => {
|
|
const pod = makeSamplePod();
|
|
const metrics = makeSampleMetrics();
|
|
mockContext({ driverInstalled: true, controllerPods: [pod] });
|
|
vi.mocked(fetchControllerMetrics).mockResolvedValueOnce(metrics);
|
|
render(<MetricsPage />);
|
|
await waitFor(() => {
|
|
expect(screen.getByText('WebSocket Health')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText('Volume Operations')).toBeInTheDocument();
|
|
expect(screen.getByText('CSI Operations')).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays correct WebSocket metric data', async () => {
|
|
const pod = makeSamplePod();
|
|
const metrics = makeSampleMetrics({
|
|
websocketConnected: 1,
|
|
websocketReconnectsTotal: 42,
|
|
websocketMessagesTotal: [{ labels: {}, value: 250 }],
|
|
// Zero out other metrics to avoid number collisions
|
|
volumeOperationsTotal: [],
|
|
volumeCapacityBytes: [],
|
|
csiOperationsTotal: [],
|
|
});
|
|
mockContext({ driverInstalled: true, controllerPods: [pod] });
|
|
vi.mocked(fetchControllerMetrics).mockResolvedValueOnce(metrics);
|
|
render(<MetricsPage />);
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Connected')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText('42')).toBeInTheDocument(); // reconnects
|
|
expect(screen.getByText('250')).toBeInTheDocument(); // messages
|
|
});
|
|
|
|
it('displays CSI operations broken down by method', async () => {
|
|
const pod = makeSamplePod();
|
|
const metrics = makeSampleMetrics({
|
|
csiOperationsTotal: [
|
|
{ labels: { method: 'CreateVolume' }, value: 77 },
|
|
{ labels: { method: 'DeleteVolume' }, value: 13 },
|
|
],
|
|
// Zero out other metrics to avoid number collisions
|
|
volumeOperationsTotal: [],
|
|
volumeCapacityBytes: [],
|
|
websocketMessagesTotal: [],
|
|
});
|
|
mockContext({ driverInstalled: true, controllerPods: [pod] });
|
|
vi.mocked(fetchControllerMetrics).mockResolvedValueOnce(metrics);
|
|
render(<MetricsPage />);
|
|
await waitFor(() => {
|
|
expect(screen.getByText('CreateVolume')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText('77')).toBeInTheDocument();
|
|
expect(screen.getByText('DeleteVolume')).toBeInTheDocument();
|
|
expect(screen.getByText('13')).toBeInTheDocument();
|
|
});
|
|
|
|
it('refresh button triggers refetch', async () => {
|
|
const pod = makeSamplePod();
|
|
const metrics = makeSampleMetrics();
|
|
mockContext({ driverInstalled: true, controllerPods: [pod] });
|
|
vi.mocked(fetchControllerMetrics).mockResolvedValue(metrics);
|
|
render(<MetricsPage />);
|
|
await waitFor(() => {
|
|
expect(screen.getByText('WebSocket Health')).toBeInTheDocument();
|
|
});
|
|
const initialCallCount = vi.mocked(fetchControllerMetrics).mock.calls.length;
|
|
fireEvent.click(screen.getByLabelText('Refresh metrics'));
|
|
await waitFor(() => {
|
|
expect(vi.mocked(fetchControllerMetrics).mock.calls.length).toBeGreaterThan(initialCallCount);
|
|
});
|
|
});
|
|
|
|
it('shows "Updated" timestamp after successful fetch', async () => {
|
|
const pod = makeSamplePod();
|
|
const metrics = makeSampleMetrics();
|
|
mockContext({ driverInstalled: true, controllerPods: [pod] });
|
|
vi.mocked(fetchControllerMetrics).mockResolvedValueOnce(metrics);
|
|
render(<MetricsPage />);
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Updated:/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('shows volume operations grouped by protocol', async () => {
|
|
const pod = makeSamplePod();
|
|
const metrics = makeSampleMetrics({
|
|
volumeOperationsTotal: [
|
|
{ labels: { protocol: 'nfs' }, value: 15 },
|
|
{ labels: { protocol: 'iscsi' }, value: 8 },
|
|
],
|
|
});
|
|
mockContext({ driverInstalled: true, controllerPods: [pod] });
|
|
vi.mocked(fetchControllerMetrics).mockResolvedValueOnce(metrics);
|
|
render(<MetricsPage />);
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Operations (nfs)')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText('Operations (iscsi)')).toBeInTheDocument();
|
|
});
|
|
});
|