fix(admin-v2): Restore complete admin-v2 application

The admin-v2 application was incomplete in the repository. This commit
restores all missing components:

- Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education,
  infrastructure, communication, development, onboarding, rbac
- SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen,
  vendor-compliance, tom-generator, dsr, and more
- Developer portal (25 pages): API docs, SDK guides, frameworks
- All components, lib files, hooks, and types
- Updated package.json with all dependencies

The issue was caused by incomplete initial repository state - the full
admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2
but was never fully synced to the main admin-v2 directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
BreakPilot Dev
2026-02-08 23:40:15 -08:00
parent f28244753f
commit 660295e218
385 changed files with 138126 additions and 3079 deletions

View File

@@ -0,0 +1,324 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { exportToPDF, exportToZIP, downloadExport } from '../export'
import type { SDKState } from '../types'
// Mock jsPDF as a class
vi.mock('jspdf', () => {
return {
default: class MockJsPDF {
internal = {
pageSize: { getWidth: () => 210, getHeight: () => 297 },
}
setFillColor = vi.fn().mockReturnThis()
setDrawColor = vi.fn().mockReturnThis()
setTextColor = vi.fn().mockReturnThis()
setFontSize = vi.fn().mockReturnThis()
setFont = vi.fn().mockReturnThis()
setLineWidth = vi.fn().mockReturnThis()
text = vi.fn().mockReturnThis()
line = vi.fn().mockReturnThis()
rect = vi.fn().mockReturnThis()
roundedRect = vi.fn().mockReturnThis()
circle = vi.fn().mockReturnThis()
addPage = vi.fn().mockReturnThis()
setPage = vi.fn().mockReturnThis()
getNumberOfPages = vi.fn(() => 5)
splitTextToSize = vi.fn((text: string) => [text])
output = vi.fn(() => new Blob(['mock-pdf'], { type: 'application/pdf' }))
},
}
})
// Mock JSZip as a class
vi.mock('jszip', () => {
return {
default: class MockJSZip {
private mockFolder = {
file: vi.fn().mockReturnThis(),
folder: vi.fn(() => this.mockFolder),
}
folder = vi.fn(() => this.mockFolder)
generateAsync = vi.fn(() => Promise.resolve(new Blob(['mock-zip'], { type: 'application/zip' })))
},
}
})
const createMockState = (overrides: Partial<SDKState> = {}): SDKState => ({
version: '1.0.0',
lastModified: new Date('2024-01-15'),
tenantId: 'test-tenant',
userId: 'test-user',
subscription: 'PROFESSIONAL',
currentPhase: 1,
currentStep: 'use-case-workshop',
completedSteps: ['use-case-workshop', 'screening'],
checkpoints: {
'CP-UC': {
checkpointId: 'CP-UC',
passed: true,
validatedAt: new Date(),
validatedBy: 'SYSTEM',
errors: [],
warnings: [],
},
},
useCases: [
{
id: 'uc-1',
name: 'Test Use Case',
description: 'A test use case for testing',
category: 'Marketing',
stepsCompleted: 3,
steps: [
{ id: 's1', name: 'Step 1', completed: true, data: {} },
{ id: 's2', name: 'Step 2', completed: true, data: {} },
{ id: 's3', name: 'Step 3', completed: true, data: {} },
],
assessmentResult: null,
createdAt: new Date(),
updatedAt: new Date(),
},
],
activeUseCase: 'uc-1',
screening: null,
modules: [],
requirements: [],
controls: [
{
id: 'ctrl-1',
name: 'Test Control',
description: 'A test control',
type: 'TECHNICAL',
category: 'Access Control',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: [],
owner: 'Test Owner',
dueDate: null,
},
],
evidence: [],
checklist: [],
risks: [
{
id: 'risk-1',
title: 'Test Risk',
description: 'A test risk',
category: 'Security',
likelihood: 3,
impact: 4,
severity: 'HIGH',
inherentRiskScore: 12,
residualRiskScore: 6,
status: 'MITIGATED',
mitigation: [
{
id: 'mit-1',
description: 'Test mitigation',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 80,
controlId: 'ctrl-1',
},
],
owner: 'Risk Owner',
relatedControls: ['ctrl-1'],
relatedRequirements: [],
},
],
aiActClassification: null,
obligations: [],
dsfa: null,
toms: [],
retentionPolicies: [],
vvt: [],
documents: [],
cookieBanner: null,
consents: [],
dsrConfig: null,
escalationWorkflows: [],
sbom: null,
securityIssues: [],
securityBacklog: [],
commandBarHistory: [],
recentSearches: [],
preferences: {
language: 'de',
theme: 'light',
compactMode: false,
showHints: true,
autoSave: true,
autoValidate: true,
allowParallelWork: true,
},
...overrides,
})
describe('exportToPDF', () => {
it('should return a Blob', async () => {
const state = createMockState()
const result = await exportToPDF(state)
expect(result).toBeInstanceOf(Blob)
})
it('should create a PDF with the correct type', async () => {
const state = createMockState()
const result = await exportToPDF(state)
expect(result.type).toBe('application/pdf')
})
it('should handle empty state', async () => {
const emptyState = createMockState({
useCases: [],
risks: [],
controls: [],
completedSteps: [],
})
const result = await exportToPDF(emptyState)
expect(result).toBeInstanceOf(Blob)
})
it('should handle state with multiple risks of different severities', async () => {
const state = createMockState({
risks: [
{
id: 'risk-1',
title: 'Critical Risk',
description: 'Critical',
category: 'Security',
likelihood: 5,
impact: 5,
severity: 'CRITICAL',
inherentRiskScore: 25,
residualRiskScore: 15,
status: 'IDENTIFIED',
mitigation: [],
owner: null,
relatedControls: [],
relatedRequirements: [],
},
{
id: 'risk-2',
title: 'Low Risk',
description: 'Low',
category: 'Operational',
likelihood: 1,
impact: 1,
severity: 'LOW',
inherentRiskScore: 1,
residualRiskScore: 1,
status: 'ACCEPTED',
mitigation: [],
owner: null,
relatedControls: [],
relatedRequirements: [],
},
],
})
const result = await exportToPDF(state)
expect(result).toBeInstanceOf(Blob)
})
})
describe('exportToZIP', () => {
it('should return a Blob', async () => {
const state = createMockState()
const result = await exportToZIP(state)
expect(result).toBeInstanceOf(Blob)
})
it('should create a ZIP with the correct type', async () => {
const state = createMockState()
const result = await exportToZIP(state)
expect(result.type).toBe('application/zip')
})
it('should handle empty state', async () => {
const emptyState = createMockState({
useCases: [],
risks: [],
controls: [],
completedSteps: [],
})
const result = await exportToZIP(emptyState)
expect(result).toBeInstanceOf(Blob)
})
it('should respect includeEvidence option', async () => {
const state = createMockState()
const result = await exportToZIP(state, { includeEvidence: false })
expect(result).toBeInstanceOf(Blob)
})
it('should respect includeDocuments option', async () => {
const state = createMockState()
const result = await exportToZIP(state, { includeDocuments: false })
expect(result).toBeInstanceOf(Blob)
})
})
describe('downloadExport', () => {
let mockCreateElement: ReturnType<typeof vi.spyOn>
let mockAppendChild: ReturnType<typeof vi.spyOn>
let mockRemoveChild: ReturnType<typeof vi.spyOn>
let mockLink: { href: string; download: string; click: ReturnType<typeof vi.fn> }
beforeEach(() => {
mockLink = {
href: '',
download: '',
click: vi.fn(),
}
mockCreateElement = vi.spyOn(document, 'createElement').mockReturnValue(mockLink as unknown as HTMLElement)
mockAppendChild = vi.spyOn(document.body, 'appendChild').mockImplementation(() => mockLink as unknown as HTMLElement)
mockRemoveChild = vi.spyOn(document.body, 'removeChild').mockImplementation(() => mockLink as unknown as HTMLElement)
})
it('should download JSON format', async () => {
const state = createMockState()
await downloadExport(state, 'json')
expect(mockLink.download).toContain('.json')
expect(mockLink.click).toHaveBeenCalled()
})
it('should download PDF format', async () => {
const state = createMockState()
await downloadExport(state, 'pdf')
expect(mockLink.download).toContain('.pdf')
expect(mockLink.click).toHaveBeenCalled()
})
it('should download ZIP format', async () => {
const state = createMockState()
await downloadExport(state, 'zip')
expect(mockLink.download).toContain('.zip')
expect(mockLink.click).toHaveBeenCalled()
})
it('should include date in filename', async () => {
const state = createMockState()
await downloadExport(state, 'json')
// Check that filename contains a date pattern
expect(mockLink.download).toMatch(/ai-compliance-sdk-\d{4}-\d{2}-\d{2}\.json/)
})
it('should throw error for unknown format', async () => {
const state = createMockState()
await expect(downloadExport(state, 'unknown' as any)).rejects.toThrow('Unknown export format')
})
})

View File

@@ -0,0 +1,250 @@
import { describe, it, expect } from 'vitest'
import {
SDK_STEPS,
getStepById,
getStepByUrl,
getNextStep,
getPreviousStep,
getCompletionPercentage,
getPhaseCompletionPercentage,
type SDKState,
} from '../types'
describe('SDK_STEPS', () => {
it('should have steps defined for both phases', () => {
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1)
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2)
expect(phase1Steps.length).toBeGreaterThan(0)
expect(phase2Steps.length).toBeGreaterThan(0)
})
it('should have unique IDs for all steps', () => {
const ids = SDK_STEPS.map(s => s.id)
const uniqueIds = new Set(ids)
expect(uniqueIds.size).toBe(ids.length)
})
it('should have unique URLs for all steps', () => {
const urls = SDK_STEPS.map(s => s.url)
const uniqueUrls = new Set(urls)
expect(uniqueUrls.size).toBe(urls.length)
})
it('should have checkpoint IDs for all steps', () => {
SDK_STEPS.forEach(step => {
expect(step.checkpointId).toBeDefined()
expect(step.checkpointId.length).toBeGreaterThan(0)
})
})
})
describe('getStepById', () => {
it('should return the correct step for a valid ID', () => {
const step = getStepById('use-case-workshop')
expect(step).toBeDefined()
expect(step?.name).toBe('Use Case Workshop')
})
it('should return undefined for an invalid ID', () => {
const step = getStepById('invalid-step-id')
expect(step).toBeUndefined()
})
it('should find steps in Phase 2', () => {
const step = getStepById('dsfa')
expect(step).toBeDefined()
expect(step?.phase).toBe(2)
})
})
describe('getStepByUrl', () => {
it('should return the correct step for a valid URL', () => {
const step = getStepByUrl('/sdk/advisory-board')
expect(step).toBeDefined()
expect(step?.id).toBe('use-case-workshop')
})
it('should return undefined for an invalid URL', () => {
const step = getStepByUrl('/invalid/url')
expect(step).toBeUndefined()
})
it('should find Phase 2 steps by URL', () => {
const step = getStepByUrl('/sdk/dsfa')
expect(step).toBeDefined()
expect(step?.id).toBe('dsfa')
})
})
describe('getNextStep', () => {
it('should return the next step in sequence', () => {
const nextStep = getNextStep('use-case-workshop')
expect(nextStep).toBeDefined()
expect(nextStep?.id).toBe('screening')
})
it('should return undefined for the last step', () => {
const lastStep = SDK_STEPS[SDK_STEPS.length - 1]
const nextStep = getNextStep(lastStep.id)
expect(nextStep).toBeUndefined()
})
it('should handle transition between phases', () => {
const lastPhase1Step = SDK_STEPS.filter(s => s.phase === 1).pop()
expect(lastPhase1Step).toBeDefined()
const nextStep = getNextStep(lastPhase1Step!.id)
expect(nextStep?.phase).toBe(2)
})
})
describe('getPreviousStep', () => {
it('should return the previous step in sequence', () => {
const prevStep = getPreviousStep('screening')
expect(prevStep).toBeDefined()
expect(prevStep?.id).toBe('use-case-workshop')
})
it('should return undefined for the first step', () => {
const prevStep = getPreviousStep('use-case-workshop')
expect(prevStep).toBeUndefined()
})
})
describe('getCompletionPercentage', () => {
const createMockState = (completedSteps: string[]): SDKState => ({
version: '1.0.0',
lastModified: new Date(),
tenantId: 'test',
userId: 'test',
subscription: 'PROFESSIONAL',
currentPhase: 1,
currentStep: 'use-case-workshop',
completedSteps,
checkpoints: {},
useCases: [],
activeUseCase: null,
screening: null,
modules: [],
requirements: [],
controls: [],
evidence: [],
checklist: [],
risks: [],
aiActClassification: null,
obligations: [],
dsfa: null,
toms: [],
retentionPolicies: [],
vvt: [],
documents: [],
cookieBanner: null,
consents: [],
dsrConfig: null,
escalationWorkflows: [],
sbom: null,
securityIssues: [],
securityBacklog: [],
commandBarHistory: [],
recentSearches: [],
preferences: {
language: 'de',
theme: 'light',
compactMode: false,
showHints: true,
autoSave: true,
autoValidate: true,
allowParallelWork: true,
},
})
it('should return 0 for no completed steps', () => {
const state = createMockState([])
const percentage = getCompletionPercentage(state)
expect(percentage).toBe(0)
})
it('should return 100 for all completed steps', () => {
const allStepIds = SDK_STEPS.map(s => s.id)
const state = createMockState(allStepIds)
const percentage = getCompletionPercentage(state)
expect(percentage).toBe(100)
})
it('should calculate correct percentage for partial completion', () => {
const halfSteps = SDK_STEPS.slice(0, Math.floor(SDK_STEPS.length / 2)).map(s => s.id)
const state = createMockState(halfSteps)
const percentage = getCompletionPercentage(state)
expect(percentage).toBeGreaterThan(40)
expect(percentage).toBeLessThan(60)
})
})
describe('getPhaseCompletionPercentage', () => {
const createMockState = (completedSteps: string[]): SDKState => ({
version: '1.0.0',
lastModified: new Date(),
tenantId: 'test',
userId: 'test',
subscription: 'PROFESSIONAL',
currentPhase: 1,
currentStep: 'use-case-workshop',
completedSteps,
checkpoints: {},
useCases: [],
activeUseCase: null,
screening: null,
modules: [],
requirements: [],
controls: [],
evidence: [],
checklist: [],
risks: [],
aiActClassification: null,
obligations: [],
dsfa: null,
toms: [],
retentionPolicies: [],
vvt: [],
documents: [],
cookieBanner: null,
consents: [],
dsrConfig: null,
escalationWorkflows: [],
sbom: null,
securityIssues: [],
securityBacklog: [],
commandBarHistory: [],
recentSearches: [],
preferences: {
language: 'de',
theme: 'light',
compactMode: false,
showHints: true,
autoSave: true,
autoValidate: true,
allowParallelWork: true,
},
})
it('should return 0 for Phase 1 with no completed steps', () => {
const state = createMockState([])
const percentage = getPhaseCompletionPercentage(state, 1)
expect(percentage).toBe(0)
})
it('should return 100 for Phase 1 when all Phase 1 steps are complete', () => {
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1).map(s => s.id)
const state = createMockState(phase1Steps)
const percentage = getPhaseCompletionPercentage(state, 1)
expect(percentage).toBe(100)
})
it('should not count Phase 2 steps in Phase 1 percentage', () => {
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2).map(s => s.id)
const state = createMockState(phase2Steps)
const percentage = getPhaseCompletionPercentage(state, 1)
expect(percentage).toBe(0)
})
})