diff --git a/admin-compliance/app/sdk/control-library/__tests__/helpers.test.ts b/admin-compliance/app/sdk/control-library/__tests__/helpers.test.ts new file mode 100644 index 0000000..5a35b55 --- /dev/null +++ b/admin-compliance/app/sdk/control-library/__tests__/helpers.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest' +import { getDomain, BACKEND_URL, EMPTY_CONTROL, DOMAIN_OPTIONS, COLLECTION_OPTIONS } from '../components/helpers' + +describe('getDomain', () => { + it('extracts domain from control_id', () => { + expect(getDomain('AUTH-001')).toBe('AUTH') + expect(getDomain('NET-042')).toBe('NET') + expect(getDomain('CRYPT-003')).toBe('CRYPT') + }) + + it('returns empty string for invalid control_id', () => { + expect(getDomain('')).toBe('') + expect(getDomain('NODASH')).toBe('NODASH') + }) +}) + +describe('BACKEND_URL', () => { + it('points to canonical API proxy', () => { + expect(BACKEND_URL).toBe('/api/sdk/v1/canonical') + }) +}) + +describe('EMPTY_CONTROL', () => { + it('has required fields with default values', () => { + expect(EMPTY_CONTROL.framework_id).toBe('bp_security_v1') + expect(EMPTY_CONTROL.severity).toBe('medium') + expect(EMPTY_CONTROL.release_state).toBe('draft') + expect(EMPTY_CONTROL.tags).toEqual([]) + expect(EMPTY_CONTROL.requirements).toEqual(['']) + expect(EMPTY_CONTROL.test_procedure).toEqual(['']) + expect(EMPTY_CONTROL.evidence).toEqual([{ type: '', description: '' }]) + expect(EMPTY_CONTROL.open_anchors).toEqual([{ framework: '', ref: '', url: '' }]) + }) +}) + +describe('DOMAIN_OPTIONS', () => { + it('contains expected domains', () => { + const values = DOMAIN_OPTIONS.map(d => d.value) + expect(values).toContain('AUTH') + expect(values).toContain('NET') + expect(values).toContain('CRYPT') + expect(values).toContain('AI') + expect(values).toContain('COMP') + expect(values.length).toBe(10) + }) +}) + +describe('COLLECTION_OPTIONS', () => { + it('contains expected collections', () => { + const values = COLLECTION_OPTIONS.map(c => c.value) + expect(values).toContain('bp_compliance_ce') + expect(values).toContain('bp_compliance_gesetze') + expect(values).toContain('bp_compliance_datenschutz') + expect(values.length).toBe(6) + }) +}) diff --git a/admin-compliance/app/sdk/control-library/components/ControlDetail.tsx b/admin-compliance/app/sdk/control-library/components/ControlDetail.tsx new file mode 100644 index 0000000..df8d9c1 --- /dev/null +++ b/admin-compliance/app/sdk/control-library/components/ControlDetail.tsx @@ -0,0 +1,270 @@ +'use client' + +import { + ArrowLeft, ExternalLink, BookOpen, Scale, FileText, + Eye, CheckCircle2, Trash2, Pencil, Clock, + ChevronLeft, SkipForward, +} from 'lucide-react' +import { CanonicalControl, EFFORT_LABELS, SeverityBadge, StateBadge, LicenseRuleBadge } from './helpers' + +interface ControlDetailProps { + ctrl: CanonicalControl + onBack: () => void + onEdit: () => void + onDelete: (controlId: string) => void + onReview: (controlId: string, action: string) => void + // Review mode navigation + reviewMode?: boolean + reviewIndex?: number + reviewTotal?: number + onReviewPrev?: () => void + onReviewNext?: () => void +} + +export function ControlDetail({ + ctrl, + onBack, + onEdit, + onDelete, + onReview, + reviewMode, + reviewIndex = 0, + reviewTotal = 0, + onReviewPrev, + onReviewNext, +}: ControlDetailProps) { + return ( +
{ctrl.objective}
+{ctrl.rationale}
+{k}: {v}
+ ))} ++ {ctrl.source_original_text} +
+Keine Referenzen vorhanden.
+ )} +Pfad: {String(ctrl.generation_metadata.processing_path || '-')}
+ {ctrl.generation_metadata.similarity_status && ( +Similarity: {String(ctrl.generation_metadata.similarity_status)}
+ )} + {Array.isArray(ctrl.generation_metadata.similar_controls) && ( +Aehnliche Controls:
+ {(ctrl.generation_metadata.similar_controls as Array{String(s.control_id)} — {String(s.title)} ({String(s.similarity)})
+ ))} +Format: DOMAIN-NNN (z.B. AUTH-003, NET-005)
+Jedes Control braucht mindestens eine offene Referenz (OWASP, NIST, ENISA, etc.)
+ {form.open_anchors.map((anchor, i) => ( +Keine Jobs vorhanden.
+ ) : ( +Keine Auswahl = alle Collections
+ )} +{String(genResult.message || genResult.status)}
+{e}
)} +Format: DOMAIN-NNN (z.B. AUTH-003, NET-005)
-Jedes Control braucht mindestens eine offene Referenz (OWASP, NIST, ENISA, etc.)
- {form.open_anchors.map((anchor, i) => ( -{error}
{ctrl.objective}
-{ctrl.rationale}
-Plattformen
-Komponenten
-Datenklassen
-{ev.description}
-- Dieses Control basiert auf frei verfuegbarem Wissen. Alle Referenzen sind offen und oeffentlich zugaenglich. -
-Quelle: {ctrl.source_citation.source}
- {ctrl.source_citation.license &&Lizenz: {ctrl.source_citation.license}
} - {ctrl.source_citation.license_notice &&Hinweis: {ctrl.source_citation.license_notice}
} - {ctrl.source_citation.url && ( - -{ctrl.source_original_text}
-
-
Pfad: {String(ctrl.generation_metadata.processing_path || '-')}
- {ctrl.generation_metadata.similarity_status && ( -Similarity: {String(ctrl.generation_metadata.similarity_status)}
- )} - {Array.isArray(ctrl.generation_metadata.similar_controls) && ( -Aehnliche Controls:
- {(ctrl.generation_metadata.similar_controls as Array{String(s.control_id)} — {String(s.title)} ({String(s.similarity)})
- ))} -