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(); 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[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(); expect(screen.getByTestId('loader')).toHaveTextContent('Loading tns-csi data...'); }); it('shows "Driver Not Detected" when driver not installed', () => { mockContext({ driverInstalled: false }); render(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); await waitFor(() => { expect(screen.getByText('Operations (nfs)')).toBeInTheDocument(); }); expect(screen.getByText('Operations (iscsi)')).toBeInTheDocument(); }); });