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

@@ -1,355 +0,0 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
// Mock fetch globally
const mockFetch = vi.fn()
global.fetch = mockFetch
describe('DSFA API Client', () => {
beforeEach(() => {
mockFetch.mockClear()
})
afterEach(() => {
vi.restoreAllMocks()
})
describe('listDSFAs', () => {
it('should fetch DSFAs without status filter', async () => {
const mockDSFAs = [
{ id: 'dsfa-1', name: 'Test DSFA 1', status: 'draft' },
{ id: 'dsfa-2', name: 'Test DSFA 2', status: 'approved' },
]
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ dsfas: mockDSFAs }),
})
const { listDSFAs } = await import('../api')
const result = await listDSFAs()
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result).toHaveLength(2)
expect(result[0].name).toBe('Test DSFA 1')
})
it('should fetch DSFAs with status filter', async () => {
const mockDSFAs = [{ id: 'dsfa-1', name: 'Draft DSFA', status: 'draft' }]
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ dsfas: mockDSFAs }),
})
const { listDSFAs } = await import('../api')
const result = await listDSFAs('draft')
expect(mockFetch).toHaveBeenCalledTimes(1)
const calledUrl = mockFetch.mock.calls[0][0]
expect(calledUrl).toContain('status=draft')
})
it('should return empty array when no DSFAs', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ dsfas: null }),
})
const { listDSFAs } = await import('../api')
const result = await listDSFAs()
expect(result).toEqual([])
})
})
describe('getDSFA', () => {
it('should fetch a single DSFA by ID', async () => {
const mockDSFA = {
id: 'dsfa-123',
name: 'Test DSFA',
status: 'draft',
risks: [],
mitigations: [],
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockDSFA),
})
const { getDSFA } = await import('../api')
const result = await getDSFA('dsfa-123')
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.id).toBe('dsfa-123')
expect(result.name).toBe('Test DSFA')
})
it('should throw error for non-existent DSFA', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 404,
text: () => Promise.resolve('{"error": "DSFA not found"}'),
})
const { getDSFA } = await import('../api')
await expect(getDSFA('non-existent')).rejects.toThrow()
})
})
describe('createDSFA', () => {
it('should create a new DSFA', async () => {
const newDSFA = {
name: 'New DSFA',
description: 'Test description',
processing_purpose: 'Testing',
}
const createdDSFA = {
id: 'dsfa-new',
...newDSFA,
status: 'draft',
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(createdDSFA),
})
const { createDSFA } = await import('../api')
const result = await createDSFA(newDSFA)
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.id).toBe('dsfa-new')
expect(result.name).toBe('New DSFA')
})
})
describe('updateDSFA', () => {
it('should update an existing DSFA', async () => {
const updates = {
name: 'Updated DSFA Name',
processing_purpose: 'Updated purpose',
}
const updatedDSFA = {
id: 'dsfa-123',
...updates,
status: 'draft',
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(updatedDSFA),
})
const { updateDSFA } = await import('../api')
const result = await updateDSFA('dsfa-123', updates)
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.name).toBe('Updated DSFA Name')
})
})
describe('deleteDSFA', () => {
it('should delete a DSFA', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
})
const { deleteDSFA } = await import('../api')
await deleteDSFA('dsfa-123')
expect(mockFetch).toHaveBeenCalledTimes(1)
const calledConfig = mockFetch.mock.calls[0][1]
expect(calledConfig.method).toBe('DELETE')
})
it('should throw error when deletion fails', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
statusText: 'Not Found',
})
const { deleteDSFA } = await import('../api')
await expect(deleteDSFA('non-existent')).rejects.toThrow()
})
})
describe('updateDSFASection', () => {
it('should update a specific section', async () => {
const sectionData = {
processing_purpose: 'Updated purpose',
data_categories: ['personal_data', 'contact_data'],
}
const updatedDSFA = {
id: 'dsfa-123',
section_progress: {
section_1_complete: true,
},
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(updatedDSFA),
})
const { updateDSFASection } = await import('../api')
const result = await updateDSFASection('dsfa-123', 1, sectionData)
expect(mockFetch).toHaveBeenCalledTimes(1)
const calledUrl = mockFetch.mock.calls[0][0]
expect(calledUrl).toContain('/sections/1')
})
})
describe('submitDSFAForReview', () => {
it('should submit DSFA for review', async () => {
const response = {
message: 'DSFA submitted for review',
dsfa: {
id: 'dsfa-123',
status: 'in_review',
},
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(response),
})
const { submitDSFAForReview } = await import('../api')
const result = await submitDSFAForReview('dsfa-123')
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.dsfa.status).toBe('in_review')
})
})
describe('approveDSFA', () => {
it('should approve a DSFA', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ message: 'DSFA approved' }),
})
const { approveDSFA } = await import('../api')
const result = await approveDSFA('dsfa-123', {
dpo_opinion: 'Approved after review',
approved: true,
})
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.message).toBe('DSFA approved')
})
it('should reject a DSFA', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ message: 'DSFA rejected' }),
})
const { approveDSFA } = await import('../api')
const result = await approveDSFA('dsfa-123', {
dpo_opinion: 'Needs more details',
approved: false,
})
expect(result.message).toBe('DSFA rejected')
})
})
describe('getDSFAStats', () => {
it('should fetch DSFA statistics', async () => {
const stats = {
total: 10,
status_stats: {
draft: 4,
in_review: 2,
approved: 3,
rejected: 1,
},
risk_stats: {
low: 3,
medium: 4,
high: 2,
very_high: 1,
},
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(stats),
})
const { getDSFAStats } = await import('../api')
const result = await getDSFAStats()
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.total).toBe(10)
expect(result.status_stats.approved).toBe(3)
})
})
describe('createDSFAFromAssessment', () => {
it('should create DSFA from UCCA assessment', async () => {
const response = {
dsfa: {
id: 'dsfa-new',
name: 'AI Chatbot DSFA',
status: 'draft',
},
prefilled: true,
message: 'DSFA created from assessment',
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(response),
})
const { createDSFAFromAssessment } = await import('../api')
const result = await createDSFAFromAssessment('assessment-123')
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.prefilled).toBe(true)
expect(result.dsfa.id).toBe('dsfa-new')
})
})
describe('getDSFAByAssessment', () => {
it('should return DSFA linked to assessment', async () => {
const dsfa = {
id: 'dsfa-123',
assessment_id: 'assessment-123',
name: 'Linked DSFA',
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(dsfa),
})
const { getDSFAByAssessment } = await import('../api')
const result = await getDSFAByAssessment('assessment-123')
expect(result?.id).toBe('dsfa-123')
})
it('should return null when no DSFA exists for assessment', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 404,
text: () => Promise.resolve('Not found'),
})
const { getDSFAByAssessment } = await import('../api')
const result = await getDSFAByAssessment('no-dsfa-assessment')
expect(result).toBeNull()
})
})
})

View File

@@ -1,255 +0,0 @@
import { describe, it, expect } from 'vitest'
import {
calculateRiskLevel,
DSFA_SECTIONS,
DSFA_STATUS_LABELS,
DSFA_RISK_LEVEL_LABELS,
DSFA_LEGAL_BASES,
DSFA_AFFECTED_RIGHTS,
RISK_MATRIX,
type DSFARisk,
type DSFAMitigation,
type DSFASectionProgress,
type DSFA,
} from '../types'
describe('DSFA_SECTIONS', () => {
it('should have 5 sections defined', () => {
expect(DSFA_SECTIONS.length).toBe(5)
})
it('should have sections numbered 1-5', () => {
const numbers = DSFA_SECTIONS.map(s => s.number)
expect(numbers).toEqual([1, 2, 3, 4, 5])
})
it('should have GDPR references for all sections', () => {
DSFA_SECTIONS.forEach(section => {
expect(section.gdprRef).toBeDefined()
expect(section.gdprRef).toContain('Art. 35')
})
})
it('should mark first 4 sections as required', () => {
const requiredSections = DSFA_SECTIONS.filter(s => s.required)
expect(requiredSections.length).toBe(4)
expect(requiredSections.map(s => s.number)).toEqual([1, 2, 3, 4])
})
it('should mark section 5 as optional', () => {
const section5 = DSFA_SECTIONS.find(s => s.number === 5)
expect(section5?.required).toBe(false)
})
it('should have German titles for all sections', () => {
DSFA_SECTIONS.forEach(section => {
expect(section.titleDE).toBeDefined()
expect(section.titleDE.length).toBeGreaterThan(0)
})
})
})
describe('DSFA_STATUS_LABELS', () => {
it('should have all status labels defined', () => {
expect(DSFA_STATUS_LABELS.draft).toBe('Entwurf')
expect(DSFA_STATUS_LABELS.in_review).toBe('In Prüfung')
expect(DSFA_STATUS_LABELS.approved).toBe('Genehmigt')
expect(DSFA_STATUS_LABELS.rejected).toBe('Abgelehnt')
expect(DSFA_STATUS_LABELS.needs_update).toBe('Überarbeitung erforderlich')
})
})
describe('DSFA_RISK_LEVEL_LABELS', () => {
it('should have all risk level labels defined', () => {
expect(DSFA_RISK_LEVEL_LABELS.low).toBe('Niedrig')
expect(DSFA_RISK_LEVEL_LABELS.medium).toBe('Mittel')
expect(DSFA_RISK_LEVEL_LABELS.high).toBe('Hoch')
expect(DSFA_RISK_LEVEL_LABELS.very_high).toBe('Sehr Hoch')
})
})
describe('DSFA_LEGAL_BASES', () => {
it('should have 6 legal bases defined', () => {
expect(Object.keys(DSFA_LEGAL_BASES).length).toBe(6)
})
it('should reference GDPR Article 6', () => {
Object.values(DSFA_LEGAL_BASES).forEach(label => {
expect(label).toContain('Art. 6')
})
})
})
describe('DSFA_AFFECTED_RIGHTS', () => {
it('should have multiple affected rights defined', () => {
expect(DSFA_AFFECTED_RIGHTS.length).toBeGreaterThan(5)
})
it('should have id and label for each right', () => {
DSFA_AFFECTED_RIGHTS.forEach(right => {
expect(right.id).toBeDefined()
expect(right.label).toBeDefined()
})
})
it('should include GDPR data subject rights', () => {
const ids = DSFA_AFFECTED_RIGHTS.map(r => r.id)
expect(ids).toContain('right_of_access')
expect(ids).toContain('right_to_erasure')
expect(ids).toContain('right_to_data_portability')
})
})
describe('RISK_MATRIX', () => {
it('should have 9 cells defined (3x3 matrix)', () => {
expect(RISK_MATRIX.length).toBe(9)
})
it('should cover all combinations of likelihood and impact', () => {
const likelihoodValues = ['low', 'medium', 'high']
const impactValues = ['low', 'medium', 'high']
likelihoodValues.forEach(likelihood => {
impactValues.forEach(impact => {
const cell = RISK_MATRIX.find(
c => c.likelihood === likelihood && c.impact === impact
)
expect(cell).toBeDefined()
})
})
})
it('should have increasing scores for higher risks', () => {
const lowLow = RISK_MATRIX.find(c => c.likelihood === 'low' && c.impact === 'low')
const highHigh = RISK_MATRIX.find(c => c.likelihood === 'high' && c.impact === 'high')
expect(lowLow?.score).toBeLessThan(highHigh?.score || 0)
})
})
describe('calculateRiskLevel', () => {
it('should return low for low likelihood and low impact', () => {
const result = calculateRiskLevel('low', 'low')
expect(result.level).toBe('low')
expect(result.score).toBe(10)
})
it('should return very_high for high likelihood and high impact', () => {
const result = calculateRiskLevel('high', 'high')
expect(result.level).toBe('very_high')
expect(result.score).toBe(90)
})
it('should return medium for medium likelihood and medium impact', () => {
const result = calculateRiskLevel('medium', 'medium')
expect(result.level).toBe('medium')
expect(result.score).toBe(50)
})
it('should return high for high likelihood and medium impact', () => {
const result = calculateRiskLevel('high', 'medium')
expect(result.level).toBe('high')
expect(result.score).toBe(70)
})
it('should return medium for low likelihood and high impact', () => {
const result = calculateRiskLevel('low', 'high')
expect(result.level).toBe('medium')
expect(result.score).toBe(40)
})
})
describe('DSFARisk type', () => {
it('should accept valid risk data', () => {
const risk: DSFARisk = {
id: 'risk-001',
category: 'confidentiality',
description: 'Unauthorized access to personal data',
likelihood: 'medium',
impact: 'high',
risk_level: 'high',
affected_data: ['customer_data', 'financial_data'],
}
expect(risk.id).toBe('risk-001')
expect(risk.category).toBe('confidentiality')
expect(risk.likelihood).toBe('medium')
expect(risk.impact).toBe('high')
})
})
describe('DSFAMitigation type', () => {
it('should accept valid mitigation data', () => {
const mitigation: DSFAMitigation = {
id: 'mit-001',
risk_id: 'risk-001',
description: 'Implement encryption at rest',
type: 'technical',
status: 'implemented',
residual_risk: 'low',
responsible_party: 'IT Security Team',
}
expect(mitigation.id).toBe('mit-001')
expect(mitigation.type).toBe('technical')
expect(mitigation.status).toBe('implemented')
})
})
describe('DSFASectionProgress type', () => {
it('should track completion for all 5 sections', () => {
const progress: DSFASectionProgress = {
section_1_complete: true,
section_2_complete: true,
section_3_complete: false,
section_4_complete: false,
section_5_complete: false,
}
expect(progress.section_1_complete).toBe(true)
expect(progress.section_2_complete).toBe(true)
expect(progress.section_3_complete).toBe(false)
})
})
describe('DSFA type', () => {
it('should accept a complete DSFA object', () => {
const dsfa: DSFA = {
id: 'dsfa-001',
tenant_id: 'tenant-001',
name: 'AI Chatbot DSFA',
description: 'Data Protection Impact Assessment for AI Chatbot',
processing_description: 'Automated customer service using AI',
processing_purpose: 'Customer support automation',
data_categories: ['contact_data', 'inquiry_content'],
data_subjects: ['customers'],
recipients: ['internal_staff'],
legal_basis: 'legitimate_interest',
necessity_assessment: 'Required for efficient customer service',
proportionality_assessment: 'Minimal data processing for the purpose',
risks: [],
overall_risk_level: 'medium',
risk_score: 50,
mitigations: [],
dpo_consulted: false,
authority_consulted: false,
status: 'draft',
section_progress: {
section_1_complete: true,
section_2_complete: true,
section_3_complete: false,
section_4_complete: false,
section_5_complete: false,
},
conclusion: '',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
created_by: 'user-001',
}
expect(dsfa.id).toBe('dsfa-001')
expect(dsfa.name).toBe('AI Chatbot DSFA')
expect(dsfa.status).toBe('draft')
expect(dsfa.data_categories).toHaveLength(2)
})
})