feat(sdk): Multi-Tenancy, Versionierung, Change-Requests, Dokumentengenerierung (Phase 1-6)
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 18s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 18s
6-Phasen-Implementation fuer cloud-faehiges, mandantenfaehiges Compliance SDK:
Phase 1: Multi-Tenancy Fix
- Shared tenant_utils.py Dependency (UUID-Validierung, kein "default" mehr)
- VVT tenant_id Column + tenant-scoped Queries
- DSFA/Vendor DEFAULT_TENANT_ID von "default" auf UUID migriert
- Migration 035
Phase 2: Stammdaten-Erweiterung
- Company Profile um JSONB-Felder erweitert (processing_systems, ai_systems, technical_contacts)
- Regulierungs-Flags (NIS2, AI Act, ISO 27001)
- GET /template-context Endpoint
- Migration 036
Phase 3: Dokument-Versionierung
- 5 Versions-Tabellen (DSFA, VVT, TOM, Loeschfristen, Obligations)
- Shared versioning_utils.py Helper
- /{id}/versions Endpoints auf allen 5 Dokumenttypen
- Migration 037
Phase 4: Change-Request System
- Zentrale CR-Inbox mit CRUD + Accept/Reject/Edit Workflow
- Regelbasierte CR-Engine (VVT DPIA → DSFA CR, Datenkategorien → Loeschfristen CR)
- Audit-Trail
- Migration 038
Phase 5: Dokumentengenerierung
- 5 Template-Generatoren (DSFA, VVT, TOM, Loeschfristen, Obligations)
- Preview + Apply Endpoints (erzeugt CRs, keine direkten Dokumente)
Phase 6: Frontend-Integration
- Change-Request Inbox Page mit Stats, Filtern, Modals
- VersionHistory Timeline-Komponente
- SDKSidebar CR-Badge (60s Polling)
- Company Profile: 2 neue Wizard-Steps + "Dokumente generieren" CTA
Docs: 5 neue MkDocs-Seiten, CLAUDE.md aktualisiert
Tests: 97 neue Tests (alle bestanden)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
233
backend-compliance/tests/test_generation_routes.py
Normal file
233
backend-compliance/tests/test_generation_routes.py
Normal file
@@ -0,0 +1,233 @@
|
||||
"""Tests for Document Generation (Phase 5).
|
||||
|
||||
Verifies:
|
||||
- Template generators produce correct output from context
|
||||
- DSFA template includes AI risk section
|
||||
- VVT generates one entry per processing system
|
||||
- TOM includes regulatory-specific measures
|
||||
- Loeschfristen maps standard periods
|
||||
- Obligation includes DSGVO + AI Act + NIS2 when flags set
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from compliance.api.document_templates.dsfa_template import generate_dsfa_draft
|
||||
from compliance.api.document_templates.vvt_template import generate_vvt_drafts
|
||||
from compliance.api.document_templates.loeschfristen_template import generate_loeschfristen_drafts
|
||||
from compliance.api.document_templates.tom_template import generate_tom_drafts
|
||||
from compliance.api.document_templates.obligation_template import generate_obligation_drafts
|
||||
|
||||
|
||||
def _make_ctx(**overrides):
|
||||
"""Build a realistic template context."""
|
||||
base = {
|
||||
"company_name": "Acme GmbH",
|
||||
"legal_form": "GmbH",
|
||||
"industry": "IT",
|
||||
"dpo_name": "Max Datenschutz",
|
||||
"dpo_email": "dpo@acme.de",
|
||||
"supervisory_authority": "LfDI BW",
|
||||
"review_cycle_months": 12,
|
||||
"subject_to_nis2": False,
|
||||
"subject_to_ai_act": False,
|
||||
"subject_to_iso27001": False,
|
||||
"uses_ai": False,
|
||||
"has_ai_systems": False,
|
||||
"processing_systems": [],
|
||||
"ai_systems": [],
|
||||
"ai_use_cases": [],
|
||||
"repos": [],
|
||||
"document_sources": [],
|
||||
"technical_contacts": [],
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DSFA Template
|
||||
# =============================================================================
|
||||
|
||||
class TestDSFATemplate:
|
||||
def test_basic_draft(self):
|
||||
ctx = _make_ctx()
|
||||
draft = generate_dsfa_draft(ctx)
|
||||
assert "DSFA" in draft["title"]
|
||||
assert draft["status"] == "draft"
|
||||
assert draft["involves_ai"] is False
|
||||
assert draft["risk_level"] == "medium"
|
||||
|
||||
def test_ai_draft_is_high_risk(self):
|
||||
ctx = _make_ctx(
|
||||
has_ai_systems=True,
|
||||
ai_systems=[{"name": "Chatbot", "purpose": "Support", "risk_category": "limited", "has_human_oversight": True}],
|
||||
subject_to_ai_act=True,
|
||||
)
|
||||
draft = generate_dsfa_draft(ctx)
|
||||
assert draft["involves_ai"] is True
|
||||
assert draft["risk_level"] == "high"
|
||||
assert len(draft["ai_systems_summary"]) == 1
|
||||
|
||||
def test_includes_dpo(self):
|
||||
ctx = _make_ctx(dpo_name="Dr. Privacy")
|
||||
draft = generate_dsfa_draft(ctx)
|
||||
assert draft["dpo_name"] == "Dr. Privacy"
|
||||
assert "Dr. Privacy" in draft["sections"]["section_1"]["content"]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# VVT Template
|
||||
# =============================================================================
|
||||
|
||||
class TestVVTTemplate:
|
||||
def test_generates_per_system(self):
|
||||
ctx = _make_ctx(processing_systems=[
|
||||
{"name": "SAP HR", "vendor": "SAP", "hosting": "cloud", "personal_data_categories": ["Mitarbeiter"]},
|
||||
{"name": "Salesforce", "vendor": "Salesforce", "hosting": "us-cloud", "personal_data_categories": ["Kunden"]},
|
||||
])
|
||||
drafts = generate_vvt_drafts(ctx)
|
||||
assert len(drafts) == 2
|
||||
assert drafts[0]["vvt_id"] == "VVT-AUTO-001"
|
||||
assert "SAP HR" in drafts[0]["name"]
|
||||
|
||||
def test_us_cloud_adds_third_country(self):
|
||||
ctx = _make_ctx(processing_systems=[
|
||||
{"name": "AWS", "vendor": "Amazon", "hosting": "us-cloud", "personal_data_categories": []},
|
||||
])
|
||||
drafts = generate_vvt_drafts(ctx)
|
||||
assert len(drafts[0]["third_country_transfers"]) > 0
|
||||
|
||||
def test_no_systems_no_drafts(self):
|
||||
ctx = _make_ctx(processing_systems=[])
|
||||
drafts = generate_vvt_drafts(ctx)
|
||||
assert len(drafts) == 0
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TOM Template
|
||||
# =============================================================================
|
||||
|
||||
class TestTOMTemplate:
|
||||
def test_base_toms(self):
|
||||
ctx = _make_ctx()
|
||||
drafts = generate_tom_drafts(ctx)
|
||||
assert len(drafts) == 8 # Base TOMs only
|
||||
categories = {d["category"] for d in drafts}
|
||||
assert "Zutrittskontrolle" in categories
|
||||
assert "Zugangskontrolle" in categories
|
||||
|
||||
def test_nis2_adds_cybersecurity(self):
|
||||
ctx = _make_ctx(subject_to_nis2=True)
|
||||
drafts = generate_tom_drafts(ctx)
|
||||
assert len(drafts) > 8
|
||||
categories = {d["category"] for d in drafts}
|
||||
assert "Cybersicherheit" in categories
|
||||
|
||||
def test_ai_act_adds_ki_compliance(self):
|
||||
ctx = _make_ctx(subject_to_ai_act=True)
|
||||
drafts = generate_tom_drafts(ctx)
|
||||
categories = {d["category"] for d in drafts}
|
||||
assert "KI-Compliance" in categories
|
||||
|
||||
def test_iso27001_adds_isms(self):
|
||||
ctx = _make_ctx(subject_to_iso27001=True)
|
||||
drafts = generate_tom_drafts(ctx)
|
||||
categories = {d["category"] for d in drafts}
|
||||
assert "ISMS" in categories
|
||||
|
||||
def test_all_flags_combined(self):
|
||||
ctx = _make_ctx(subject_to_nis2=True, subject_to_ai_act=True, subject_to_iso27001=True)
|
||||
drafts = generate_tom_drafts(ctx)
|
||||
# 8 base + 3 NIS2 + 3 ISO + 3 AI = 17
|
||||
assert len(drafts) == 17
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Loeschfristen Template
|
||||
# =============================================================================
|
||||
|
||||
class TestLoeschfristenTemplate:
|
||||
def test_generates_per_category(self):
|
||||
ctx = _make_ctx(processing_systems=[
|
||||
{"name": "HR", "personal_data_categories": ["Bankdaten", "Steuer-ID"]},
|
||||
{"name": "CRM", "personal_data_categories": ["Kundendaten"]},
|
||||
])
|
||||
drafts = generate_loeschfristen_drafts(ctx)
|
||||
assert len(drafts) == 3
|
||||
categories = {d["data_category"] for d in drafts}
|
||||
assert "Bankdaten" in categories
|
||||
assert "Steuer-ID" in categories
|
||||
assert "Kundendaten" in categories
|
||||
|
||||
def test_standard_periods_applied(self):
|
||||
ctx = _make_ctx(processing_systems=[
|
||||
{"name": "Payroll", "personal_data_categories": ["Bankdaten"]},
|
||||
])
|
||||
drafts = generate_loeschfristen_drafts(ctx)
|
||||
bankdaten = [d for d in drafts if d["data_category"] == "Bankdaten"][0]
|
||||
assert "10 Jahre" in bankdaten["retention_period"]
|
||||
assert "HGB" in bankdaten["legal_basis"]
|
||||
|
||||
def test_unknown_category_defaults(self):
|
||||
ctx = _make_ctx(processing_systems=[
|
||||
{"name": "Custom", "personal_data_categories": ["Spezialdaten"]},
|
||||
])
|
||||
drafts = generate_loeschfristen_drafts(ctx)
|
||||
assert drafts[0]["retention_period"] == "Noch festzulegen"
|
||||
|
||||
def test_deduplicates_categories(self):
|
||||
ctx = _make_ctx(processing_systems=[
|
||||
{"name": "A", "personal_data_categories": ["Bankdaten"]},
|
||||
{"name": "B", "personal_data_categories": ["Bankdaten"]},
|
||||
])
|
||||
drafts = generate_loeschfristen_drafts(ctx)
|
||||
assert len(drafts) == 1 # Deduplicated
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Obligation Template
|
||||
# =============================================================================
|
||||
|
||||
class TestObligationTemplate:
|
||||
def test_base_dsgvo(self):
|
||||
ctx = _make_ctx()
|
||||
drafts = generate_obligation_drafts(ctx)
|
||||
assert len(drafts) == 8 # Base DSGVO
|
||||
titles = {d["title"] for d in drafts}
|
||||
assert "Verzeichnis der Verarbeitungstätigkeiten führen" in titles
|
||||
|
||||
def test_ai_act_obligations(self):
|
||||
ctx = _make_ctx(subject_to_ai_act=True)
|
||||
drafts = generate_obligation_drafts(ctx)
|
||||
assert len(drafts) > 8
|
||||
regs = {d["regulation"] for d in drafts}
|
||||
assert "EU AI Act" in regs
|
||||
|
||||
def test_nis2_obligations(self):
|
||||
ctx = _make_ctx(subject_to_nis2=True)
|
||||
drafts = generate_obligation_drafts(ctx)
|
||||
regs = {d["regulation"] for d in drafts}
|
||||
assert "NIS2" in regs
|
||||
|
||||
def test_all_flags(self):
|
||||
ctx = _make_ctx(subject_to_nis2=True, subject_to_ai_act=True)
|
||||
drafts = generate_obligation_drafts(ctx)
|
||||
# 8 DSGVO + 3 AI + 2 NIS2 = 13
|
||||
assert len(drafts) == 13
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Route Registration
|
||||
# =============================================================================
|
||||
|
||||
class TestGenerationRouteRegistration:
|
||||
def test_routes_registered(self):
|
||||
from compliance.api import router
|
||||
paths = [r.path for r in router.routes]
|
||||
assert any("generation" in p for p in paths)
|
||||
|
||||
def test_preview_and_apply(self):
|
||||
from compliance.api.generation_routes import router
|
||||
paths = [r.path for r in router.routes]
|
||||
assert any("preview" in p for p in paths)
|
||||
assert any("apply" in p for p in paths)
|
||||
Reference in New Issue
Block a user