Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
312 lines
8.4 KiB
TypeScript
312 lines
8.4 KiB
TypeScript
/**
|
|
* DependencyMap Component Tests
|
|
*
|
|
* Tests fuer die Control-Requirement Mapping Visualisierung
|
|
*/
|
|
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
import DependencyMap from '../../components/compliance/charts/DependencyMap'
|
|
|
|
// Mock-Daten
|
|
const mockRequirements = [
|
|
{
|
|
id: 'req-1',
|
|
article: 'Art. 32',
|
|
title: 'Sicherheit der Verarbeitung',
|
|
regulation_code: 'GDPR',
|
|
},
|
|
{
|
|
id: 'req-2',
|
|
article: 'Art. 9',
|
|
title: 'Risikomanagement',
|
|
regulation_code: 'AIACT',
|
|
},
|
|
{
|
|
id: 'req-3',
|
|
article: 'Art. 25',
|
|
title: 'Datenschutz durch Technikgestaltung',
|
|
regulation_code: 'GDPR',
|
|
},
|
|
]
|
|
|
|
const mockControls = [
|
|
{
|
|
id: 'ctrl-1',
|
|
control_id: 'PRIV-001',
|
|
title: 'Verarbeitungsverzeichnis',
|
|
domain: 'priv',
|
|
status: 'pass',
|
|
},
|
|
{
|
|
id: 'ctrl-2',
|
|
control_id: 'CRYPTO-001',
|
|
title: 'Verschluesselung',
|
|
domain: 'crypto',
|
|
status: 'pass',
|
|
},
|
|
{
|
|
id: 'ctrl-3',
|
|
control_id: 'AI-001',
|
|
title: 'KI-Risikobewertung',
|
|
domain: 'ai',
|
|
status: 'partial',
|
|
},
|
|
]
|
|
|
|
const mockMappings = [
|
|
{ requirement_id: 'req-1', control_id: 'PRIV-001', coverage_level: 'full' as const },
|
|
{ requirement_id: 'req-1', control_id: 'CRYPTO-001', coverage_level: 'partial' as const },
|
|
{ requirement_id: 'req-2', control_id: 'AI-001', coverage_level: 'full' as const },
|
|
{ requirement_id: 'req-3', control_id: 'PRIV-001', coverage_level: 'planned' as const },
|
|
]
|
|
|
|
describe('DependencyMap', () => {
|
|
it('should render the component with data', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
// Statistik-Header sollte sichtbar sein
|
|
expect(screen.getByText('Abdeckung')).toBeInTheDocument()
|
|
expect(screen.getByText('Vollstaendig')).toBeInTheDocument()
|
|
expect(screen.getByText('Teilweise')).toBeInTheDocument()
|
|
expect(screen.getByText('Geplant')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render empty state when no data', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={[]}
|
|
controls={[]}
|
|
mappings={[]}
|
|
/>
|
|
)
|
|
|
|
expect(screen.getByText(/Keine Mappings vorhanden/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should show correct coverage statistics', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
// 2 full, 1 partial, 1 planned
|
|
expect(screen.getByText('2')).toBeInTheDocument() // full mappings
|
|
expect(screen.getByText('1')).toBeInTheDocument() // partial/planned mappings
|
|
})
|
|
|
|
it('should display control IDs in matrix header', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
expect(screen.getByText('PRIV-001')).toBeInTheDocument()
|
|
expect(screen.getByText('CRYPTO-001')).toBeInTheDocument()
|
|
expect(screen.getByText('AI-001')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should display requirement articles', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
expect(screen.getByText(/GDPR Art. 32/i)).toBeInTheDocument()
|
|
expect(screen.getByText(/AIACT Art. 9/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should support German language', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
lang="de"
|
|
/>
|
|
)
|
|
|
|
expect(screen.getByText('Alle Verordnungen')).toBeInTheDocument()
|
|
expect(screen.getByText('Alle Domains')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should support English language', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
lang="en"
|
|
/>
|
|
)
|
|
|
|
expect(screen.getByText('All Regulations')).toBeInTheDocument()
|
|
expect(screen.getByText('All Domains')).toBeInTheDocument()
|
|
expect(screen.getByText('Coverage')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should call onControlClick when control is clicked', () => {
|
|
const onControlClick = vi.fn()
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
onControlClick={onControlClick}
|
|
/>
|
|
)
|
|
|
|
const controlHeader = screen.getByText('PRIV-001')
|
|
fireEvent.click(controlHeader)
|
|
|
|
expect(onControlClick).toHaveBeenCalledWith(mockControls[0])
|
|
})
|
|
|
|
it('should call onRequirementClick when requirement is clicked', () => {
|
|
const onRequirementClick = vi.fn()
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
onRequirementClick={onRequirementClick}
|
|
/>
|
|
)
|
|
|
|
// Finde den Requirement-Text
|
|
const reqElement = screen.getByText(/Sicherheit der Verarbeitung/i)
|
|
fireEvent.click(reqElement.closest('div[class*="cursor-pointer"]')!)
|
|
|
|
expect(onRequirementClick).toHaveBeenCalledWith(mockRequirements[0])
|
|
})
|
|
|
|
it('should filter by regulation', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
const regSelect = screen.getByDisplayValue('Alle Verordnungen')
|
|
fireEvent.change(regSelect, { target: { value: 'AIACT' } })
|
|
|
|
// Nur AIACT Requirements sollten sichtbar sein
|
|
expect(screen.queryByText(/GDPR Art. 32/i)).not.toBeInTheDocument()
|
|
expect(screen.getByText(/AIACT Art. 9/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should filter by domain', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
const domainSelect = screen.getByDisplayValue('Alle Domains')
|
|
fireEvent.change(domainSelect, { target: { value: 'priv' } })
|
|
|
|
// Nur Privacy Controls sollten sichtbar sein
|
|
expect(screen.getByText('PRIV-001')).toBeInTheDocument()
|
|
expect(screen.queryByText('CRYPTO-001')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should switch between matrix and connection view', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
// Standard ist Matrix-Ansicht
|
|
expect(screen.getByText('Matrix')).toBeInTheDocument()
|
|
|
|
// Wechsle zu Verbindungs-Ansicht
|
|
fireEvent.click(screen.getByText('Verbindungen'))
|
|
|
|
// Connection-View Text sollte erscheinen
|
|
expect(screen.getByText(/Waehlen Sie ein Control/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should highlight connected items when control is selected', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
// Klicke auf einen Control
|
|
fireEvent.click(screen.getByText('PRIV-001'))
|
|
|
|
// Der Control sollte highlighted sein (bg-primary-100 Klasse)
|
|
const controlElement = screen.getByText('PRIV-001').closest('div')
|
|
expect(controlElement?.className).toContain('bg-primary-100')
|
|
})
|
|
|
|
it('should show legend with coverage levels', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
expect(screen.getByText('Vollstaendig abgedeckt')).toBeInTheDocument()
|
|
expect(screen.getByText('Teilweise abgedeckt')).toBeInTheDocument()
|
|
expect(screen.getByText('Geplant')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should calculate coverage percentage correctly', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
// 3 von 3 Requirements haben mindestens ein Mapping = 100%
|
|
expect(screen.getByText('100.0%')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should show control counts in connection view', () => {
|
|
render(
|
|
<DependencyMap
|
|
requirements={mockRequirements}
|
|
controls={mockControls}
|
|
mappings={mockMappings}
|
|
/>
|
|
)
|
|
|
|
// Wechsle zu Verbindungs-Ansicht
|
|
fireEvent.click(screen.getByText('Verbindungen'))
|
|
|
|
// PRIV-001 hat 2 Verbindungen
|
|
const privControl = screen.getAllByText('PRIV-001')[0].closest('button')
|
|
expect(privControl?.textContent).toContain('2')
|
|
})
|
|
})
|