import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { render, screen, waitFor, fireEvent, act } from '@testing-library/react' import ControlLibraryPage from '../page' // ============================================================================ // Mock data // ============================================================================ const MOCK_FRAMEWORK = { id: 'fw-1', framework_id: 'bp_security_v1', name: 'BreakPilot Security', version: '1.0', description: 'Test framework', release_state: 'draft', } const MOCK_CONTROL = { id: 'ctrl-1', framework_id: 'fw-1', control_id: 'AUTH-001', title: 'Multi-Factor Authentication', objective: 'Require MFA for all admin accounts.', rationale: 'Passwords alone are insufficient.', scope: {}, requirements: ['MFA for admin'], test_procedure: ['Test admin login'], evidence: [{ type: 'config', description: 'MFA enabled' }], severity: 'high', risk_score: 4.0, implementation_effort: 'm', evidence_confidence: null, open_anchors: [{ framework: 'OWASP', ref: 'V2.8', url: 'https://owasp.org' }], release_state: 'draft', tags: ['mfa'], license_rule: 1, source_original_text: null, source_citation: { source: 'DSGVO' }, customer_visible: true, verification_method: 'automated', category: 'authentication', target_audience: 'developer', generation_metadata: null, generation_strategy: 'ungrouped', created_at: '2026-03-15T10:00:00+00:00', updated_at: '2026-03-15T10:00:00+00:00', } const MOCK_META = { total: 1, domains: [{ domain: 'AUTH', count: 1 }], sources: [{ source: 'DSGVO', count: 1 }], no_source_count: 0, } // ============================================================================ // Fetch mock // ============================================================================ function createFetchMock(overrides?: Record) { const responses: Record = { frameworks: [MOCK_FRAMEWORK], controls: [MOCK_CONTROL], 'controls-count': { total: 1 }, 'controls-meta': MOCK_META, ...overrides, } return vi.fn((url: string) => { const urlStr = typeof url === 'string' ? url : '' // Match endpoint param const match = urlStr.match(/endpoint=([^&]+)/) const endpoint = match?.[1] || '' const data = responses[endpoint] ?? [] return Promise.resolve({ ok: true, status: 200, json: () => Promise.resolve(data), }) }) } // ============================================================================ // Tests // ============================================================================ describe('ControlLibraryPage', () => { let fetchMock: ReturnType beforeEach(() => { fetchMock = createFetchMock() global.fetch = fetchMock as unknown as typeof fetch }) afterEach(() => { vi.restoreAllMocks() }) it('renders the page header', async () => { render() await waitFor(() => { expect(screen.getByText('Canonical Control Library')).toBeInTheDocument() }) }) it('shows control count from meta', async () => { fetchMock = createFetchMock({ 'controls-meta': { ...MOCK_META, total: 42 } }) global.fetch = fetchMock as unknown as typeof fetch render() await waitFor(() => { expect(screen.getByText(/42 Security Controls/)).toBeInTheDocument() }) }) it('renders control list with data', async () => { render() await waitFor(() => { expect(screen.getByText('AUTH-001')).toBeInTheDocument() expect(screen.getByText('Multi-Factor Authentication')).toBeInTheDocument() }) }) it('shows timestamp on control cards', async () => { render() await waitFor(() => { // The date should be rendered in German locale format expect(screen.getByText(/15\.03\.26/)).toBeInTheDocument() }) }) it('shows source citation on control cards', async () => { render() await waitFor(() => { expect(screen.getByText('DSGVO')).toBeInTheDocument() }) }) it('fetches with limit and offset params', async () => { render() await waitFor(() => { expect(fetchMock).toHaveBeenCalled() }) // Find the controls fetch call const controlsCalls = fetchMock.mock.calls.filter( (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('endpoint=controls&') ) expect(controlsCalls.length).toBeGreaterThan(0) const url = controlsCalls[0][0] as string expect(url).toContain('limit=50') expect(url).toContain('offset=0') }) it('fetches controls-count alongside controls', async () => { render() await waitFor(() => { const countCalls = fetchMock.mock.calls.filter( (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('endpoint=controls-count') ) expect(countCalls.length).toBeGreaterThan(0) }) }) it('fetches controls-meta on mount', async () => { render() await waitFor(() => { const metaCalls = fetchMock.mock.calls.filter( (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('endpoint=controls-meta') ) expect(metaCalls.length).toBeGreaterThan(0) }) }) it('renders domain dropdown from meta', async () => { render() await waitFor(() => { expect(screen.getByText('AUTH (1)')).toBeInTheDocument() }) }) it('renders source dropdown from meta', async () => { render() await waitFor(() => { // The source option should appear in the dropdown expect(screen.getByText('DSGVO (1)')).toBeInTheDocument() }) }) it('has sort dropdown with all sort options', async () => { render() await waitFor(() => { expect(screen.getByText('Sortierung: ID')).toBeInTheDocument() expect(screen.getByText('Nach Quelle')).toBeInTheDocument() expect(screen.getByText('Neueste zuerst')).toBeInTheDocument() expect(screen.getByText('Aelteste zuerst')).toBeInTheDocument() }) }) it('sends sort params when sorting by newest', async () => { render() await waitFor(() => { expect(screen.getByText('AUTH-001')).toBeInTheDocument() }) // Clear previous calls fetchMock.mockClear() // Change sort to newest const sortSelect = screen.getByDisplayValue('Sortierung: ID') await act(async () => { fireEvent.change(sortSelect, { target: { value: 'newest' } }) }) await waitFor(() => { const controlsCalls = fetchMock.mock.calls.filter( (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('endpoint=controls&') ) expect(controlsCalls.length).toBeGreaterThan(0) const url = controlsCalls[0][0] as string expect(url).toContain('sort=created_at') expect(url).toContain('order=desc') }) }) it('sends search param after debounce', async () => { render() await waitFor(() => { expect(screen.getByText('AUTH-001')).toBeInTheDocument() }) fetchMock.mockClear() const searchInput = screen.getByPlaceholderText(/Controls durchsuchen/) await act(async () => { fireEvent.change(searchInput, { target: { value: 'encryption' } }) }) // Wait for debounce (400ms) await waitFor( () => { const controlsCalls = fetchMock.mock.calls.filter( (call: unknown[]) => typeof call[0] === 'string' && (call[0] as string).includes('search=encryption') ) expect(controlsCalls.length).toBeGreaterThan(0) }, { timeout: 1000 } ) }) it('shows empty state when no controls', async () => { fetchMock = createFetchMock({ controls: [], 'controls-count': { total: 0 }, 'controls-meta': { ...MOCK_META, total: 0 }, }) global.fetch = fetchMock as unknown as typeof fetch render() await waitFor(() => { expect(screen.getByText(/Noch keine Controls/)).toBeInTheDocument() }) }) it('shows "Keine Controls gefunden" when filter matches nothing', async () => { fetchMock = createFetchMock({ controls: [], 'controls-count': { total: 0 }, 'controls-meta': { ...MOCK_META, total: 50 }, }) global.fetch = fetchMock as unknown as typeof fetch render() // Wait for initial load to finish await waitFor(() => { expect(screen.getByPlaceholderText(/Controls durchsuchen/)).toBeInTheDocument() }) // Trigger a search to have a filter active const searchInput = screen.getByPlaceholderText(/Controls durchsuchen/) await act(async () => { fireEvent.change(searchInput, { target: { value: 'zzzzzzz' } }) }) await waitFor( () => { expect(screen.getByText('Keine Controls gefunden.')).toBeInTheDocument() }, { timeout: 1000 } ) }) it('has a refresh button', async () => { render() await waitFor(() => { expect(screen.getByTitle('Aktualisieren')).toBeInTheDocument() }) }) it('renders pagination info', async () => { render() await waitFor(() => { expect(screen.getByText(/Seite 1 von 1/)).toBeInTheDocument() }) }) it('shows pagination buttons for many controls', async () => { fetchMock = createFetchMock({ 'controls-count': { total: 150 }, 'controls-meta': { ...MOCK_META, total: 150 }, }) global.fetch = fetchMock as unknown as typeof fetch render() await waitFor(() => { expect(screen.getByText(/Seite 1 von 3/)).toBeInTheDocument() }) }) })