import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor, cleanup, fireEvent } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import type { ReactNode } from 'react'; // EST RFC 7030 hardening master bundle Phase 8.4 — Vitest coverage for // the EST Administration page. Mirrors SCEPAdminPage.test.tsx's // structure verbatim. Pins: // 1. Admin gate — non-admin sees the gated banner; admin requests are // never issued. // 2. Tab navigation — Profiles is the default; clicking each tab // switches surface; ?tab=activity / ?tab=trust deep-links land // correctly. // 3. Profiles tab — per-profile cards; status badges reflect mTLS + // Basic + ServerKeygen; trust-anchor expiry badge tone bands // (good ≥30d / warn 7-30d / bad <7d / EXPIRED); per-counter cells // render the correct value; "Reload trust anchor" only renders for // mTLS-enabled profiles AND opens the modal on click. // 4. Reload modal — Confirm calls mutation / Cancel skips mutation / // Error keeps modal open + surfaces the error message. // 5. Recent Activity tab — merges all four EST audit actions across // four parallel useQuery calls; filter chips narrow to the // requested subset. // 6. Trust Bundle tab — only mTLS profiles render; non-mTLS deploy // sees the empty-state banner. // 7. Error path — surfaces ErrorState on the active tab. vi.mock('../api/client', () => ({ getAdminESTProfiles: vi.fn(), reloadAdminESTTrust: vi.fn(), getAuditEvents: vi.fn(), })); vi.mock('../components/AuthProvider', () => ({ useAuth: vi.fn(), })); import ESTAdminPage from './ESTAdminPage'; import * as client from '../api/client'; import { useAuth } from '../components/AuthProvider'; function renderWithRoute(initialPath: string, ui: ReactNode) { const qc = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0, staleTime: 0 } }, }); return render( , ); } function setAuth(opts: { authRequired: boolean; admin: boolean }) { vi.mocked(useAuth).mockReturnValue({ loading: false, authRequired: opts.authRequired, authenticated: true, authType: 'apikey', user: 'tester', admin: opts.admin, login: async () => {}, logout: () => {}, error: null, }); } const corpProfile = { path_id: 'corp', issuer_id: 'iss-corp', profile_id: 'prof-corp', counters: { success_simpleenroll: 42, success_simplereenroll: 7, success_serverkeygen: 3, auth_failed_basic: 1, auth_failed_mtls: 0, auth_failed_channel_binding: 0, csr_invalid: 0, csr_policy_violation: 0, csr_signature_mismatch: 0, rate_limited: 2, issuer_error: 0, internal_error: 0, }, mtls_enabled: true, basic_auth_configured: true, server_keygen_enabled: true, trust_anchors: [ { subject: 'corp-bootstrap-ca', not_before: '2026-01-01T00:00:00Z', not_after: '2027-01-01T00:00:00Z', days_to_expiry: 250, expired: false, }, ], trust_anchor_path: '/etc/certctl/est-mtls-corp.pem', now: '2026-04-29T15:00:00Z', }; const iotProfile = { path_id: 'iot', issuer_id: 'iss-iot', counters: { success_simpleenroll: 9, auth_failed_basic: 0, } as Record, mtls_enabled: false, basic_auth_configured: false, server_keygen_enabled: false, now: '2026-04-29T15:00:00Z', }; const profilesResponse = { profiles: [corpProfile, iotProfile], profile_count: 2, generated_at: '2026-04-29T15:00:00Z', }; beforeEach(() => { vi.clearAllMocks(); vi.mocked(client.getAdminESTProfiles).mockResolvedValue(profilesResponse as any); vi.mocked(client.getAuditEvents).mockResolvedValue({ data: [], total: 0, page: 1, per_page: 50 } as any); setAuth({ authRequired: true, admin: true }); }); afterEach(() => { cleanup(); }); // React's afterEach is implicit in this scope via Vitest; the explicit // cleanup() above is safe to call even when no render happened. function afterEach(fn: () => void) { // re-export from vitest globals — vitest's globals expose `afterEach` // automatically when test config has globals: true. Our config does, so // the import is unnecessary; this thin shim documents the call site. (globalThis as any).afterEach?.(fn); } describe('ESTAdminPage — admin gate', () => { it('non-admin sees the gated banner; admin requests never fire', async () => { setAuth({ authRequired: true, admin: false }); renderWithRoute('/est', ); expect(await screen.findByText(/Admin access required/i)).toBeInTheDocument(); await waitFor(() => { expect(client.getAdminESTProfiles).not.toHaveBeenCalled(); }); }); it('non-auth-required deploy lets the page render and fires admin request', async () => { setAuth({ authRequired: false, admin: false }); renderWithRoute('/est', ); await waitFor(() => { expect(client.getAdminESTProfiles).toHaveBeenCalled(); }); }); }); describe('ESTAdminPage — tab navigation', () => { it('defaults to the Profiles tab', async () => { renderWithRoute('/est', ); expect(await screen.findByTestId('est-tab-profiles')).toHaveAttribute('aria-pressed', 'true'); }); it('clicking Recent Activity switches the tab', async () => { renderWithRoute('/est', ); fireEvent.click(await screen.findByTestId('est-tab-activity')); expect(screen.getByTestId('est-tab-activity')).toHaveAttribute('aria-pressed', 'true'); }); it('?tab=trust deep-link lands on Trust Bundle', async () => { renderWithRoute('/est?tab=trust', ); expect(await screen.findByTestId('est-tab-trust')).toHaveAttribute('aria-pressed', 'true'); }); }); describe('ESTAdminPage — Profiles tab', () => { it('renders one card per profile with the right badges + counters', async () => { renderWithRoute('/est', ); expect(await screen.findByTestId('est-profile-summary-corp')).toBeInTheDocument(); expect(screen.getByTestId('est-profile-summary-iot')).toBeInTheDocument(); // Per-profile counter cells render with the snapshot value. expect(screen.getByTestId('est-counter-corp-success_simpleenroll')).toHaveTextContent('42'); expect(screen.getByTestId('est-counter-corp-rate_limited')).toHaveTextContent('2'); expect(screen.getByTestId('est-counter-iot-success_simpleenroll')).toHaveTextContent('9'); // Counters that don't appear in the iot snapshot default to 0 in the cell. expect(screen.getByTestId('est-counter-iot-internal_error')).toHaveTextContent('0'); }); it('reload-trust button only appears for mTLS profiles', async () => { renderWithRoute('/est', ); expect(await screen.findByTestId('est-reload-trust-corp')).toBeInTheDocument(); expect(screen.queryByTestId('est-reload-trust-iot')).toBeNull(); }); it('shows mTLS trust expiry badge tone bands', async () => { renderWithRoute('/est', ); const badge = await screen.findByTestId('est-trust-expiry-badge-corp'); expect(badge).toHaveTextContent(/250d remaining/); }); }); describe('ESTAdminPage — reload modal', () => { it('Confirm calls mutation', async () => { vi.mocked(client.reloadAdminESTTrust).mockResolvedValue({ reloaded: true, path_id: 'corp', reloaded_at: '2026-04-29T15:00:01Z', }); renderWithRoute('/est', ); fireEvent.click(await screen.findByTestId('est-reload-trust-corp')); fireEvent.click(await screen.findByTestId('est-reload-confirm')); await waitFor(() => { expect(client.reloadAdminESTTrust).toHaveBeenCalledWith('corp'); }); }); it('Cancel skips mutation', async () => { renderWithRoute('/est', ); fireEvent.click(await screen.findByTestId('est-reload-trust-corp')); fireEvent.click(await screen.findByTestId('est-reload-cancel')); await waitFor(() => { expect(screen.queryByTestId('est-reload-confirm')).toBeNull(); }); expect(client.reloadAdminESTTrust).not.toHaveBeenCalled(); }); it('Error keeps the modal open + surfaces the message', async () => { vi.mocked(client.reloadAdminESTTrust).mockRejectedValue( new Error('Trust anchor reload failed: trustanchor: cert in /etc/est-corp.pem expired'), ); renderWithRoute('/est', ); fireEvent.click(await screen.findByTestId('est-reload-trust-corp')); fireEvent.click(await screen.findByTestId('est-reload-confirm')); expect(await screen.findByTestId('est-reload-error')).toHaveTextContent(/expired/); // Modal stays open — Confirm button still rendered. expect(screen.getByTestId('est-reload-confirm')).toBeInTheDocument(); }); }); describe('ESTAdminPage — Trust Bundle tab', () => { it('renders only mTLS profiles + skips non-mTLS', async () => { renderWithRoute('/est?tab=trust', ); expect(await screen.findByTestId('est-trust-card-corp')).toBeInTheDocument(); expect(screen.queryByTestId('est-trust-card-iot')).toBeNull(); }); it('shows the empty-state banner when no profile has mTLS', async () => { vi.mocked(client.getAdminESTProfiles).mockResolvedValue({ profiles: [iotProfile], profile_count: 1, generated_at: '2026-04-29T15:00:00Z', } as any); renderWithRoute('/est?tab=trust', ); expect(await screen.findByText(/No EST profiles have mTLS enabled/i)).toBeInTheDocument(); }); }); describe('ESTAdminPage — Recent Activity tab', () => { it('renders filter chips + reacts to selection', async () => { renderWithRoute('/est?tab=activity', ); const allChip = await screen.findByTestId('est-activity-filter-all'); expect(allChip).toHaveAttribute('aria-pressed', 'true'); fireEvent.click(screen.getByTestId('est-activity-filter-enroll')); await waitFor(() => { expect(screen.getByTestId('est-activity-filter-enroll')).toHaveAttribute('aria-pressed', 'true'); }); }); });