test+docs: add policy library tests (67 tests) and MKDocs documentation
All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 34s
CI/CD / test-python-backend-compliance (push) Successful in 35s
CI/CD / test-python-document-crawler (push) Successful in 23s
CI/CD / test-python-dsms-gateway (push) Successful in 19s
CI/CD / validate-canonical-controls (push) Successful in 12s
CI/CD / Deploy (push) Successful in 2s
All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 34s
CI/CD / test-python-backend-compliance (push) Successful in 35s
CI/CD / test-python-document-crawler (push) Successful in 23s
CI/CD / test-python-dsms-gateway (push) Successful in 19s
CI/CD / validate-canonical-controls (push) Successful in 12s
CI/CD / Deploy (push) Successful in 2s
- New test_policy_templates.py: 67 tests covering all 29 policy types, API creation, filtering, placeholders, seed script validation - Updated test_legal_template_routes.py: fix type count 16→52 - New MKDocs page policy-bibliothek.md with full template reference - Updated dokumentengenerierung.md and rechtliche-texte.md with cross-refs - Added policy-bibliothek to mkdocs.yml navigation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -121,7 +121,7 @@ class TestLegalTemplateSchemas:
|
||||
assert d == {"status": "archived", "title": "Neue DSE"}
|
||||
|
||||
def test_valid_document_types_constant(self):
|
||||
"""VALID_DOCUMENT_TYPES contains all 16 expected types (post-Migration 020)."""
|
||||
"""VALID_DOCUMENT_TYPES contains all 52 expected types (Migration 020+051+054)."""
|
||||
# Original types
|
||||
assert "privacy_policy" in VALID_DOCUMENT_TYPES
|
||||
assert "terms_of_service" in VALID_DOCUMENT_TYPES
|
||||
@@ -141,7 +141,20 @@ class TestLegalTemplateSchemas:
|
||||
assert "cookie_banner" in VALID_DOCUMENT_TYPES
|
||||
assert "agb" in VALID_DOCUMENT_TYPES
|
||||
assert "clause" in VALID_DOCUMENT_TYPES
|
||||
assert len(VALID_DOCUMENT_TYPES) == 16
|
||||
# Security concepts (Migration 051)
|
||||
assert "it_security_concept" in VALID_DOCUMENT_TYPES
|
||||
assert "data_protection_concept" in VALID_DOCUMENT_TYPES
|
||||
assert "backup_recovery_concept" in VALID_DOCUMENT_TYPES
|
||||
assert "logging_concept" in VALID_DOCUMENT_TYPES
|
||||
assert "incident_response_plan" in VALID_DOCUMENT_TYPES
|
||||
assert "access_control_concept" in VALID_DOCUMENT_TYPES
|
||||
assert "risk_management_concept" in VALID_DOCUMENT_TYPES
|
||||
# Policy templates (Migration 054) — spot check
|
||||
assert "information_security_policy" in VALID_DOCUMENT_TYPES
|
||||
assert "data_protection_policy" in VALID_DOCUMENT_TYPES
|
||||
assert "business_continuity_policy" in VALID_DOCUMENT_TYPES
|
||||
# Total: 16 original + 7 security concepts + 29 policies = 52
|
||||
assert len(VALID_DOCUMENT_TYPES) == 52
|
||||
# Old names must NOT be present after rename
|
||||
assert "data_processing_agreement" not in VALID_DOCUMENT_TYPES
|
||||
assert "withdrawal_policy" not in VALID_DOCUMENT_TYPES
|
||||
@@ -488,9 +501,9 @@ class TestLegalTemplateSeed:
|
||||
class TestLegalTemplateNewTypes:
|
||||
"""Validate new document types added in Migration 020."""
|
||||
|
||||
def test_all_16_types_present(self):
|
||||
"""VALID_DOCUMENT_TYPES has exactly 16 entries."""
|
||||
assert len(VALID_DOCUMENT_TYPES) == 16
|
||||
def test_all_52_types_present(self):
|
||||
"""VALID_DOCUMENT_TYPES has exactly 52 entries (16 + 7 security + 29 policies)."""
|
||||
assert len(VALID_DOCUMENT_TYPES) == 52
|
||||
|
||||
def test_new_types_are_valid(self):
|
||||
"""All Migration 020 new types are accepted."""
|
||||
|
||||
580
backend-compliance/tests/test_policy_templates.py
Normal file
580
backend-compliance/tests/test_policy_templates.py
Normal file
@@ -0,0 +1,580 @@
|
||||
"""Tests for policy template types (Migration 054) — 29 policy templates."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
from fastapi.testclient import TestClient
|
||||
from fastapi import FastAPI
|
||||
from datetime import datetime
|
||||
|
||||
from compliance.api.legal_template_routes import (
|
||||
VALID_DOCUMENT_TYPES,
|
||||
VALID_STATUSES,
|
||||
router,
|
||||
)
|
||||
from compliance.api.db_utils import row_to_dict as _row_to_dict
|
||||
from classroom_engine.database import get_db
|
||||
from compliance.api.tenant_utils import get_tenant_id
|
||||
|
||||
DEFAULT_TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
|
||||
|
||||
# =============================================================================
|
||||
# Test App Setup
|
||||
# =============================================================================
|
||||
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
|
||||
mock_db = MagicMock()
|
||||
|
||||
|
||||
def override_get_db():
|
||||
yield mock_db
|
||||
|
||||
|
||||
def override_tenant():
|
||||
return DEFAULT_TENANT_ID
|
||||
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
app.dependency_overrides[get_tenant_id] = override_tenant
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
# =============================================================================
|
||||
# Policy type constants (grouped by category)
|
||||
# =============================================================================
|
||||
|
||||
IT_SECURITY_POLICIES = [
|
||||
"information_security_policy",
|
||||
"access_control_policy",
|
||||
"password_policy",
|
||||
"encryption_policy",
|
||||
"logging_policy",
|
||||
"backup_policy",
|
||||
"incident_response_policy",
|
||||
"change_management_policy",
|
||||
"patch_management_policy",
|
||||
"asset_management_policy",
|
||||
"cloud_security_policy",
|
||||
"devsecops_policy",
|
||||
"secrets_management_policy",
|
||||
"vulnerability_management_policy",
|
||||
]
|
||||
|
||||
DATA_POLICIES = [
|
||||
"data_protection_policy",
|
||||
"data_classification_policy",
|
||||
"data_retention_policy",
|
||||
"data_transfer_policy",
|
||||
"privacy_incident_policy",
|
||||
]
|
||||
|
||||
PERSONNEL_POLICIES = [
|
||||
"employee_security_policy",
|
||||
"security_awareness_policy",
|
||||
"remote_work_policy",
|
||||
"offboarding_policy",
|
||||
]
|
||||
|
||||
VENDOR_POLICIES = [
|
||||
"vendor_risk_management_policy",
|
||||
"third_party_security_policy",
|
||||
"supplier_security_policy",
|
||||
]
|
||||
|
||||
BCM_POLICIES = [
|
||||
"business_continuity_policy",
|
||||
"disaster_recovery_policy",
|
||||
"crisis_management_policy",
|
||||
]
|
||||
|
||||
ALL_POLICY_TYPES = (
|
||||
IT_SECURITY_POLICIES
|
||||
+ DATA_POLICIES
|
||||
+ PERSONNEL_POLICIES
|
||||
+ VENDOR_POLICIES
|
||||
+ BCM_POLICIES
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Helpers
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def make_policy_row(doc_type, title="Test Policy", content="# Test", **overrides):
|
||||
data = {
|
||||
"id": "policy-001",
|
||||
"tenant_id": DEFAULT_TENANT_ID,
|
||||
"document_type": doc_type,
|
||||
"title": title,
|
||||
"description": f"Test {doc_type}",
|
||||
"content": content,
|
||||
"placeholders": ["{{COMPANY_NAME}}", "{{SECURITY_OFFICER}}", "{{VERSION}}", "{{DATE}}"],
|
||||
"language": "de",
|
||||
"jurisdiction": "DE",
|
||||
"status": "published",
|
||||
"license_id": "mit",
|
||||
"license_name": "MIT License",
|
||||
"source_name": "BreakPilot Compliance",
|
||||
"attribution_required": False,
|
||||
"is_complete_document": True,
|
||||
"version": "1.0.0",
|
||||
"source_url": None,
|
||||
"source_repo": None,
|
||||
"source_file_path": None,
|
||||
"source_retrieved_at": None,
|
||||
"attribution_text": None,
|
||||
"inspiration_sources": [],
|
||||
"created_at": datetime(2026, 3, 14),
|
||||
"updated_at": datetime(2026, 3, 14),
|
||||
}
|
||||
data.update(overrides)
|
||||
row = MagicMock()
|
||||
row._mapping = data
|
||||
return row
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TestPolicyTypeValidation
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestPolicyTypeValidation:
|
||||
"""Verify all 29 policy types are accepted by VALID_DOCUMENT_TYPES."""
|
||||
|
||||
def test_all_29_policy_types_present(self):
|
||||
"""All 29 policy types from Migration 054 are in VALID_DOCUMENT_TYPES."""
|
||||
for doc_type in ALL_POLICY_TYPES:
|
||||
assert doc_type in VALID_DOCUMENT_TYPES, (
|
||||
f"Policy type '{doc_type}' missing from VALID_DOCUMENT_TYPES"
|
||||
)
|
||||
|
||||
def test_policy_count(self):
|
||||
"""There are exactly 29 policy template types."""
|
||||
assert len(ALL_POLICY_TYPES) == 29
|
||||
|
||||
def test_it_security_policy_count(self):
|
||||
"""IT Security category has 14 policy types."""
|
||||
assert len(IT_SECURITY_POLICIES) == 14
|
||||
|
||||
def test_data_policy_count(self):
|
||||
"""Data category has 5 policy types."""
|
||||
assert len(DATA_POLICIES) == 5
|
||||
|
||||
def test_personnel_policy_count(self):
|
||||
"""Personnel category has 4 policy types."""
|
||||
assert len(PERSONNEL_POLICIES) == 4
|
||||
|
||||
def test_vendor_policy_count(self):
|
||||
"""Vendor/Supply Chain category has 3 policy types."""
|
||||
assert len(VENDOR_POLICIES) == 3
|
||||
|
||||
def test_bcm_policy_count(self):
|
||||
"""BCM category has 3 policy types."""
|
||||
assert len(BCM_POLICIES) == 3
|
||||
|
||||
def test_total_valid_types_count(self):
|
||||
"""VALID_DOCUMENT_TYPES has 52 entries total (16 original + 7 security + 29 policies)."""
|
||||
assert len(VALID_DOCUMENT_TYPES) == 52
|
||||
|
||||
def test_no_duplicate_policy_types(self):
|
||||
"""No duplicate entries in the policy type lists."""
|
||||
assert len(ALL_POLICY_TYPES) == len(set(ALL_POLICY_TYPES))
|
||||
|
||||
def test_policies_distinct_from_security_concepts(self):
|
||||
"""Policy types are distinct from security concept types (Migration 051)."""
|
||||
security_concepts = [
|
||||
"it_security_concept", "data_protection_concept",
|
||||
"backup_recovery_concept", "logging_concept",
|
||||
"incident_response_plan", "access_control_concept",
|
||||
"risk_management_concept",
|
||||
]
|
||||
for policy_type in ALL_POLICY_TYPES:
|
||||
assert policy_type not in security_concepts, (
|
||||
f"Policy type '{policy_type}' clashes with security concept"
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TestPolicyTemplateCreation
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestPolicyTemplateCreation:
|
||||
"""Test creating policy templates via API."""
|
||||
|
||||
def setup_method(self):
|
||||
mock_db.reset_mock()
|
||||
|
||||
def test_create_information_security_policy(self):
|
||||
"""POST /legal-templates accepts information_security_policy."""
|
||||
row = make_policy_row("information_security_policy", "Informationssicherheits-Richtlinie")
|
||||
mock_db.execute.return_value.fetchone.return_value = row
|
||||
mock_db.commit = MagicMock()
|
||||
|
||||
resp = client.post("/legal-templates", json={
|
||||
"document_type": "information_security_policy",
|
||||
"title": "Informationssicherheits-Richtlinie",
|
||||
"content": "# Informationssicherheits-Richtlinie\n\n## 1. Zweck",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
|
||||
def test_create_data_protection_policy(self):
|
||||
"""POST /legal-templates accepts data_protection_policy."""
|
||||
row = make_policy_row("data_protection_policy", "Datenschutz-Richtlinie")
|
||||
mock_db.execute.return_value.fetchone.return_value = row
|
||||
mock_db.commit = MagicMock()
|
||||
|
||||
resp = client.post("/legal-templates", json={
|
||||
"document_type": "data_protection_policy",
|
||||
"title": "Datenschutz-Richtlinie",
|
||||
"content": "# Datenschutz-Richtlinie",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
|
||||
def test_create_business_continuity_policy(self):
|
||||
"""POST /legal-templates accepts business_continuity_policy."""
|
||||
row = make_policy_row("business_continuity_policy", "Business-Continuity-Richtlinie")
|
||||
mock_db.execute.return_value.fetchone.return_value = row
|
||||
mock_db.commit = MagicMock()
|
||||
|
||||
resp = client.post("/legal-templates", json={
|
||||
"document_type": "business_continuity_policy",
|
||||
"title": "Business-Continuity-Richtlinie",
|
||||
"content": "# Business-Continuity-Richtlinie",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
|
||||
def test_create_vendor_risk_management_policy(self):
|
||||
"""POST /legal-templates accepts vendor_risk_management_policy."""
|
||||
row = make_policy_row("vendor_risk_management_policy", "Lieferanten-Risikomanagement")
|
||||
mock_db.execute.return_value.fetchone.return_value = row
|
||||
mock_db.commit = MagicMock()
|
||||
|
||||
resp = client.post("/legal-templates", json={
|
||||
"document_type": "vendor_risk_management_policy",
|
||||
"title": "Lieferanten-Risikomanagement-Richtlinie",
|
||||
"content": "# Lieferanten-Risikomanagement",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
|
||||
def test_create_employee_security_policy(self):
|
||||
"""POST /legal-templates accepts employee_security_policy."""
|
||||
row = make_policy_row("employee_security_policy", "Mitarbeiter-Sicherheitsrichtlinie")
|
||||
mock_db.execute.return_value.fetchone.return_value = row
|
||||
mock_db.commit = MagicMock()
|
||||
|
||||
resp = client.post("/legal-templates", json={
|
||||
"document_type": "employee_security_policy",
|
||||
"title": "Mitarbeiter-Sicherheitsrichtlinie",
|
||||
"content": "# Mitarbeiter-Sicherheitsrichtlinie",
|
||||
})
|
||||
assert resp.status_code == 201
|
||||
|
||||
@pytest.mark.parametrize("doc_type", ALL_POLICY_TYPES)
|
||||
def test_all_policy_types_accepted_by_api(self, doc_type):
|
||||
"""POST /legal-templates accepts every policy type (parametrized)."""
|
||||
row = make_policy_row(doc_type)
|
||||
mock_db.execute.return_value.fetchone.return_value = row
|
||||
mock_db.commit = MagicMock()
|
||||
|
||||
resp = client.post("/legal-templates", json={
|
||||
"document_type": doc_type,
|
||||
"title": f"Test {doc_type}",
|
||||
"content": f"# {doc_type}",
|
||||
})
|
||||
assert resp.status_code == 201, (
|
||||
f"Expected 201 for {doc_type}, got {resp.status_code}: {resp.text}"
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TestPolicyTemplateFilter
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestPolicyTemplateFilter:
|
||||
"""Verify filtering templates by policy document types."""
|
||||
|
||||
def setup_method(self):
|
||||
mock_db.reset_mock()
|
||||
|
||||
@pytest.mark.parametrize("doc_type", [
|
||||
"information_security_policy",
|
||||
"data_protection_policy",
|
||||
"employee_security_policy",
|
||||
"vendor_risk_management_policy",
|
||||
"business_continuity_policy",
|
||||
])
|
||||
def test_filter_by_policy_type(self, doc_type):
|
||||
"""GET /legal-templates?document_type={policy} returns 200."""
|
||||
count_mock = MagicMock()
|
||||
count_mock.__getitem__ = lambda self, i: 1
|
||||
first_call = MagicMock()
|
||||
first_call.fetchone.return_value = count_mock
|
||||
second_call = MagicMock()
|
||||
second_call.fetchall.return_value = [make_policy_row(doc_type)]
|
||||
mock_db.execute.side_effect = [first_call, second_call]
|
||||
|
||||
resp = client.get(f"/legal-templates?document_type={doc_type}")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "templates" in data
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TestPolicyTemplatePlaceholders
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestPolicyTemplatePlaceholders:
|
||||
"""Verify placeholder structure for policy templates."""
|
||||
|
||||
def test_information_security_policy_placeholders(self):
|
||||
"""Information security policy has standard placeholders."""
|
||||
row = make_policy_row(
|
||||
"information_security_policy",
|
||||
placeholders=[
|
||||
"{{COMPANY_NAME}}", "{{SECURITY_OFFICER}}",
|
||||
"{{VERSION}}", "{{DATE}}",
|
||||
"{{SCOPE_DESCRIPTION}}", "{{GF_NAME}}",
|
||||
],
|
||||
)
|
||||
result = _row_to_dict(row)
|
||||
assert "{{COMPANY_NAME}}" in result["placeholders"]
|
||||
assert "{{SECURITY_OFFICER}}" in result["placeholders"]
|
||||
assert "{{GF_NAME}}" in result["placeholders"]
|
||||
|
||||
def test_data_protection_policy_placeholders(self):
|
||||
"""Data protection policy has DSB and DPO placeholders."""
|
||||
row = make_policy_row(
|
||||
"data_protection_policy",
|
||||
placeholders=[
|
||||
"{{COMPANY_NAME}}", "{{DSB_NAME}}",
|
||||
"{{DSB_EMAIL}}", "{{VERSION}}", "{{DATE}}",
|
||||
"{{GF_NAME}}", "{{SCOPE_DESCRIPTION}}",
|
||||
],
|
||||
)
|
||||
result = _row_to_dict(row)
|
||||
assert "{{DSB_NAME}}" in result["placeholders"]
|
||||
assert "{{DSB_EMAIL}}" in result["placeholders"]
|
||||
|
||||
def test_password_policy_placeholders(self):
|
||||
"""Password policy has complexity-related placeholders."""
|
||||
row = make_policy_row(
|
||||
"password_policy",
|
||||
placeholders=[
|
||||
"{{COMPANY_NAME}}", "{{SECURITY_OFFICER}}",
|
||||
"{{VERSION}}", "{{DATE}}",
|
||||
"{{MIN_PASSWORD_LENGTH}}", "{{MAX_AGE_DAYS}}",
|
||||
"{{HISTORY_COUNT}}", "{{GF_NAME}}",
|
||||
],
|
||||
)
|
||||
result = _row_to_dict(row)
|
||||
assert "{{MIN_PASSWORD_LENGTH}}" in result["placeholders"]
|
||||
assert "{{MAX_AGE_DAYS}}" in result["placeholders"]
|
||||
|
||||
def test_backup_policy_placeholders(self):
|
||||
"""Backup policy has retention-related placeholders."""
|
||||
row = make_policy_row(
|
||||
"backup_policy",
|
||||
placeholders=[
|
||||
"{{COMPANY_NAME}}", "{{SECURITY_OFFICER}}",
|
||||
"{{VERSION}}", "{{DATE}}",
|
||||
"{{RPO_HOURS}}", "{{RTO_HOURS}}",
|
||||
"{{BACKUP_RETENTION_DAYS}}", "{{GF_NAME}}",
|
||||
],
|
||||
)
|
||||
result = _row_to_dict(row)
|
||||
assert "{{RPO_HOURS}}" in result["placeholders"]
|
||||
assert "{{RTO_HOURS}}" in result["placeholders"]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TestPolicyTemplateStructure
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestPolicyTemplateStructure:
|
||||
"""Validate structural aspects of policy templates."""
|
||||
|
||||
def test_policy_uses_mit_license(self):
|
||||
"""Policy templates use MIT license."""
|
||||
row = make_policy_row("information_security_policy")
|
||||
result = _row_to_dict(row)
|
||||
assert result["license_id"] == "mit"
|
||||
assert result["license_name"] == "MIT License"
|
||||
assert result["attribution_required"] is False
|
||||
|
||||
def test_policy_language_de(self):
|
||||
"""Policy templates default to German language."""
|
||||
row = make_policy_row("access_control_policy")
|
||||
result = _row_to_dict(row)
|
||||
assert result["language"] == "de"
|
||||
assert result["jurisdiction"] == "DE"
|
||||
|
||||
def test_policy_is_complete_document(self):
|
||||
"""Policy templates are complete documents."""
|
||||
row = make_policy_row("encryption_policy")
|
||||
result = _row_to_dict(row)
|
||||
assert result["is_complete_document"] is True
|
||||
|
||||
def test_policy_default_status_published(self):
|
||||
"""Policy templates default to published status."""
|
||||
row = make_policy_row("logging_policy")
|
||||
result = _row_to_dict(row)
|
||||
assert result["status"] == "published"
|
||||
|
||||
def test_policy_row_to_dict_datetime(self):
|
||||
"""_row_to_dict converts datetime for policy rows."""
|
||||
row = make_policy_row("patch_management_policy")
|
||||
result = _row_to_dict(row)
|
||||
assert result["created_at"] == "2026-03-14T00:00:00"
|
||||
|
||||
def test_policy_source_name(self):
|
||||
"""Policy templates have BreakPilot Compliance as source."""
|
||||
row = make_policy_row("cloud_security_policy")
|
||||
result = _row_to_dict(row)
|
||||
assert result["source_name"] == "BreakPilot Compliance"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TestPolicyTemplateRejection
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestPolicyTemplateRejection:
|
||||
"""Verify invalid policy types are rejected."""
|
||||
|
||||
def setup_method(self):
|
||||
mock_db.reset_mock()
|
||||
|
||||
def test_reject_fake_policy_type(self):
|
||||
"""POST /legal-templates rejects non-existent policy type."""
|
||||
resp = client.post("/legal-templates", json={
|
||||
"document_type": "fake_security_policy",
|
||||
"title": "Fake Policy",
|
||||
"content": "# Fake",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert "Invalid document_type" in resp.json()["detail"]
|
||||
|
||||
def test_reject_policy_with_typo(self):
|
||||
"""POST /legal-templates rejects misspelled policy type."""
|
||||
resp = client.post("/legal-templates", json={
|
||||
"document_type": "informaton_security_policy",
|
||||
"title": "Typo Policy",
|
||||
"content": "# Typo",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
|
||||
def test_reject_policy_with_invalid_status(self):
|
||||
"""POST /legal-templates rejects invalid status for policy."""
|
||||
resp = client.post("/legal-templates", json={
|
||||
"document_type": "password_policy",
|
||||
"title": "Password Policy",
|
||||
"content": "# Password",
|
||||
"status": "active",
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TestPolicySeedScript
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestPolicySeedScript:
|
||||
"""Validate the seed_policy_templates.py script structure."""
|
||||
|
||||
def test_seed_script_exists(self):
|
||||
"""Seed script file exists."""
|
||||
import os
|
||||
path = os.path.join(
|
||||
os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
|
||||
)
|
||||
assert os.path.exists(path), "seed_policy_templates.py not found"
|
||||
|
||||
def test_seed_script_importable(self):
|
||||
"""Seed script can be parsed without errors."""
|
||||
import importlib.util
|
||||
import os
|
||||
path = os.path.join(
|
||||
os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
|
||||
)
|
||||
spec = importlib.util.spec_from_file_location("seed_policy_templates", path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
# Don't execute main() — just verify the module parses
|
||||
# We do this by checking TEMPLATES is defined
|
||||
try:
|
||||
spec.loader.exec_module(mod)
|
||||
except SystemExit:
|
||||
pass # Script may call sys.exit
|
||||
except Exception:
|
||||
pass # Network calls may fail in test env
|
||||
# Module should define TEMPLATES list
|
||||
assert hasattr(mod, "TEMPLATES"), "TEMPLATES list not found in seed script"
|
||||
assert len(mod.TEMPLATES) == 29, f"Expected 29 templates, got {len(mod.TEMPLATES)}"
|
||||
|
||||
def test_seed_templates_have_required_fields(self):
|
||||
"""Each seed template has document_type, title, description, content, placeholders."""
|
||||
import importlib.util
|
||||
import os
|
||||
path = os.path.join(
|
||||
os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
|
||||
)
|
||||
spec = importlib.util.spec_from_file_location("seed_policy_templates", path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
try:
|
||||
spec.loader.exec_module(mod)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
required_fields = {"document_type", "title", "description", "content", "placeholders"}
|
||||
for tmpl in mod.TEMPLATES:
|
||||
for field in required_fields:
|
||||
assert field in tmpl, (
|
||||
f"Template '{tmpl.get('document_type', '?')}' missing field '{field}'"
|
||||
)
|
||||
|
||||
def test_seed_templates_use_valid_types(self):
|
||||
"""All seed template document_types are in VALID_DOCUMENT_TYPES."""
|
||||
import importlib.util
|
||||
import os
|
||||
path = os.path.join(
|
||||
os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
|
||||
)
|
||||
spec = importlib.util.spec_from_file_location("seed_policy_templates", path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
try:
|
||||
spec.loader.exec_module(mod)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for tmpl in mod.TEMPLATES:
|
||||
assert tmpl["document_type"] in VALID_DOCUMENT_TYPES, (
|
||||
f"Seed type '{tmpl['document_type']}' not in VALID_DOCUMENT_TYPES"
|
||||
)
|
||||
|
||||
def test_seed_templates_have_german_content(self):
|
||||
"""All seed templates have German content (contain common German words)."""
|
||||
import importlib.util
|
||||
import os
|
||||
path = os.path.join(
|
||||
os.path.dirname(__file__), "..", "scripts", "seed_policy_templates.py"
|
||||
)
|
||||
spec = importlib.util.spec_from_file_location("seed_policy_templates", path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
try:
|
||||
spec.loader.exec_module(mod)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
german_markers = ["Richtlinie", "Zweck", "Geltungsbereich", "Verantwortlich"]
|
||||
for tmpl in mod.TEMPLATES:
|
||||
content = tmpl["content"]
|
||||
has_german = any(marker in content for marker in german_markers)
|
||||
assert has_german, (
|
||||
f"Template '{tmpl['document_type']}' content appears not to be German"
|
||||
)
|
||||
@@ -80,3 +80,13 @@ Im Company-Profile-Wizard erscheint nach Abschluss (`is_complete = true`) ein CT
|
||||
- Alle 5 Template-Generatoren mit verschiedenen Kontext-Variationen
|
||||
- Regulierungs-Flag-Kombinationen
|
||||
- Route-Registrierung
|
||||
|
||||
---
|
||||
|
||||
## Policy-Bibliothek
|
||||
|
||||
Neben der automatischen Dokumentengenerierung aus Stammdaten stehen **29 deutsche Richtlinien-Templates**
|
||||
im Dokumentengenerator als Vorlagen bereit (IT-Sicherheit, Datenschutz, Personal, Lieferanten, BCM).
|
||||
|
||||
Siehe [Policy-Bibliothek](policy-bibliothek.md) fuer die vollstaendige Liste aller Templates,
|
||||
Platzhalter und Kategorien.
|
||||
|
||||
224
docs-src/services/sdk-modules/policy-bibliothek.md
Normal file
224
docs-src/services/sdk-modules/policy-bibliothek.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Policy-Bibliothek (Migration 054)
|
||||
|
||||
Die Policy-Bibliothek stellt **29 deutsche Richtlinien-Templates** bereit, die ueber den Dokumentengenerator
|
||||
als Vorlagen genutzt werden koennen. Alle Templates sind nach deutschen Compliance-Standards strukturiert
|
||||
und enthalten Platzhalter fuer unternehmensspezifische Anpassung.
|
||||
|
||||
---
|
||||
|
||||
## Uebersicht
|
||||
|
||||
| Kategorie | Anzahl | UI-Pill | Beschreibung |
|
||||
|-----------|--------|---------|--------------|
|
||||
| [IT-Sicherheit](#it-sicherheit-policies) | 14 | `IT-Sicherheit Policies` | Informationssicherheit, Zugriffskontrollen, Verschluesselung, Patch-Mgmt. |
|
||||
| [Daten-Policies](#daten-policies) | 5 | `Daten-Policies` | Datenschutz, Klassifizierung, Aufbewahrung, Transfer |
|
||||
| [Personal-Policies](#personal-policies) | 4 | `Personal-Policies` | Mitarbeitersicherheit, Awareness, Remote Work, Offboarding |
|
||||
| [Lieferanten-Policies](#lieferanten-policies) | 3 | `Lieferanten-Policies` | Vendor Risk, Drittanbieter, Lieferanten-Sicherheit |
|
||||
| [BCM/Notfall](#bcmnotfall) | 3 | `BCM/Notfall` | Business Continuity, Disaster Recovery, Krisenmanagement |
|
||||
|
||||
**Gesamt:** 29 Policy-Templates + 7 Sicherheitskonzepte (Migration 051) + 16 Basis-Dokumenttypen = **52 Dokumenttypen**
|
||||
|
||||
---
|
||||
|
||||
## Template-Struktur
|
||||
|
||||
Alle 29 Policy-Templates folgen einer einheitlichen **9-Abschnitte-Struktur:**
|
||||
|
||||
1. **Zweck** — Zielsetzung der Richtlinie
|
||||
2. **Geltungsbereich** — Fuer wen gilt die Richtlinie
|
||||
3. **Begriffe** — Definitionen
|
||||
4. **Verantwortlichkeiten** — Rollen und Zustaendigkeiten
|
||||
5. **Richtlinie** (Kern) — Die eigentlichen Regelungen
|
||||
6. **Massnahmen** — Konkrete Umsetzungsschritte
|
||||
7. **Ueberwachung & Audit** — Pruef- und Kontrollmechanismen
|
||||
8. **Schulung** — Anforderungen an Mitarbeiterschulung
|
||||
9. **Inkrafttreten** — Gueltigkeitsdatum und Freigabe
|
||||
|
||||
### Gemeinsame Platzhalter
|
||||
|
||||
| Platzhalter | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| `{{COMPANY_NAME}}` | Firmenname |
|
||||
| `{{SECURITY_OFFICER}}` / `{{ISB_NAME}}` | Informationssicherheitsbeauftragter |
|
||||
| `{{GF_NAME}}` | Geschaeftsfuehrung (Freigabe) |
|
||||
| `{{VERSION}}` | Dokumentversion |
|
||||
| `{{DATE}}` | Erstellungsdatum |
|
||||
| `{{SCOPE_DESCRIPTION}}` | Geltungsbereich |
|
||||
| `{{NEXT_REVIEW_DATE}}` | Naechster Prueftermin |
|
||||
|
||||
---
|
||||
|
||||
## IT-Sicherheit Policies
|
||||
|
||||
14 Templates fuer die IT-Sicherheitsorganisation:
|
||||
|
||||
| Typ | Titel | Spezifische Platzhalter |
|
||||
|-----|-------|------------------------|
|
||||
| `information_security_policy` | Informationssicherheits-Richtlinie | `SCOPE_DESCRIPTION` |
|
||||
| `access_control_policy` | Zugriffskontrollen-Richtlinie | `REVIEW_INTERVAL_DAYS` |
|
||||
| `password_policy` | Passwort-Richtlinie | `MIN_PASSWORD_LENGTH`, `MAX_AGE_DAYS`, `HISTORY_COUNT` |
|
||||
| `encryption_policy` | Verschluesselungs-Richtlinie | `MIN_KEY_LENGTH` |
|
||||
| `logging_policy` | Logging-Richtlinie | `LOG_RETENTION_DAYS` |
|
||||
| `backup_policy` | Backup-Richtlinie | `RPO_HOURS`, `RTO_HOURS`, `BACKUP_RETENTION_DAYS` |
|
||||
| `incident_response_policy` | Incident-Response-Richtlinie | `INCIDENT_HOTLINE`, `NOTIFICATION_HOURS` |
|
||||
| `change_management_policy` | Change-Management-Richtlinie | `CAB_SCHEDULE` |
|
||||
| `patch_management_policy` | Patch-Management-Richtlinie | `CRITICAL_PATCH_HOURS`, `PATCH_WINDOW` |
|
||||
| `asset_management_policy` | Asset-Management-Richtlinie | `INVENTORY_TOOL` |
|
||||
| `cloud_security_policy` | Cloud-Sicherheits-Richtlinie | `APPROVED_PROVIDERS` |
|
||||
| `devsecops_policy` | DevSecOps-Richtlinie | `CI_TOOL`, `SAST_TOOL` |
|
||||
| `secrets_management_policy` | Secrets-Management-Richtlinie | `VAULT_URL`, `ROTATION_DAYS` |
|
||||
| `vulnerability_management_policy` | Schwachstellenmanagement-Richtlinie | `SCAN_FREQUENCY`, `SCANNER_TOOL` |
|
||||
|
||||
---
|
||||
|
||||
## Daten-Policies
|
||||
|
||||
5 Templates fuer Datenschutz und Datenmanagement:
|
||||
|
||||
| Typ | Titel | Spezifische Platzhalter |
|
||||
|-----|-------|------------------------|
|
||||
| `data_protection_policy` | Datenschutz-Richtlinie | `DSB_NAME`, `DSB_EMAIL`, `SUPERVISORY_AUTHORITY` |
|
||||
| `data_classification_policy` | Datenklassifizierungs-Richtlinie | `CLASSIFICATION_TOOL` |
|
||||
| `data_retention_policy` | Datenaufbewahrungs-Richtlinie | `DEFAULT_RETENTION_YEARS`, `DELETION_TOOL` |
|
||||
| `data_transfer_policy` | Datentransfer-Richtlinie | `DPO_EMAIL` |
|
||||
| `privacy_incident_policy` | Datenschutzvorfall-Richtlinie | `DPO_EMAIL`, `NOTIFICATION_HOURS` |
|
||||
|
||||
---
|
||||
|
||||
## Personal-Policies
|
||||
|
||||
4 Templates fuer Mitarbeitersicherheit:
|
||||
|
||||
| Typ | Titel | Spezifische Platzhalter |
|
||||
|-----|-------|------------------------|
|
||||
| `employee_security_policy` | Mitarbeiter-Sicherheitsrichtlinie | `HR_CONTACT` |
|
||||
| `security_awareness_policy` | Security-Awareness-Richtlinie | `TRAINING_FREQUENCY`, `LMS_URL` |
|
||||
| `remote_work_policy` | Remote-Work-Richtlinie | `VPN_TOOL`, `MDM_TOOL` |
|
||||
| `offboarding_policy` | Offboarding-Richtlinie | `HR_CONTACT`, `IT_CONTACT` |
|
||||
|
||||
---
|
||||
|
||||
## Lieferanten-Policies
|
||||
|
||||
3 Templates fuer die Lieferkette:
|
||||
|
||||
| Typ | Titel | Spezifische Platzhalter |
|
||||
|-----|-------|------------------------|
|
||||
| `vendor_risk_management_policy` | Lieferanten-Risikomanagement-Richtlinie | `PROCUREMENT_CONTACT`, `REVIEW_FREQUENCY` |
|
||||
| `third_party_security_policy` | Drittanbieter-Sicherheitsrichtlinie | `SECURITY_CONTACT` |
|
||||
| `supplier_security_policy` | Lieferanten-Sicherheitsrichtlinie | `PROCUREMENT_CONTACT` |
|
||||
|
||||
---
|
||||
|
||||
## BCM/Notfall
|
||||
|
||||
3 Templates fuer Business Continuity:
|
||||
|
||||
| Typ | Titel | Spezifische Platzhalter |
|
||||
|-----|-------|------------------------|
|
||||
| `business_continuity_policy` | Business-Continuity-Richtlinie | `RTO_HOURS`, `RPO_HOURS`, `BC_COORDINATOR` |
|
||||
| `disaster_recovery_policy` | Disaster-Recovery-Richtlinie | `DR_SITE`, `RTO_HOURS`, `RPO_HOURS` |
|
||||
| `crisis_management_policy` | Krisenmanagement-Richtlinie | `CRISIS_HOTLINE`, `CRISIS_TEAM_LEAD` |
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
Die Policy-Templates werden ueber die bestehende Legal-Templates-API verwaltet:
|
||||
|
||||
| Methode | Pfad | Beschreibung |
|
||||
|---------|------|--------------|
|
||||
| `GET` | `/api/compliance/legal-templates` | Templates listen (Filter: `document_type`, `status`, `language`) |
|
||||
| `GET` | `/api/compliance/legal-templates/status` | Anzahl nach Typ und Status |
|
||||
| `GET` | `/api/compliance/legal-templates/{id}` | Einzelnes Template laden |
|
||||
| `POST` | `/api/compliance/legal-templates` | Neues Template erstellen |
|
||||
| `PUT` | `/api/compliance/legal-templates/{id}` | Template aktualisieren |
|
||||
| `DELETE` | `/api/compliance/legal-templates/{id}` | Template loeschen |
|
||||
|
||||
### Beispiel: Policy-Templates nach Kategorie filtern
|
||||
|
||||
```bash
|
||||
# Alle IT-Sicherheit Policies
|
||||
curl "https://api-dev.breakpilot.ai/api/compliance/legal-templates?document_type=information_security_policy"
|
||||
|
||||
# Alle Policy-Templates (mehrere Typen)
|
||||
curl "https://api-dev.breakpilot.ai/api/compliance/legal-templates/status"
|
||||
```
|
||||
|
||||
### Beispiel: Template erstellen
|
||||
|
||||
```bash
|
||||
curl -X POST "https://api-dev.breakpilot.ai/api/compliance/legal-templates" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Tenant-Id: <tenant-id>" \
|
||||
-d '{
|
||||
"document_type": "password_policy",
|
||||
"title": "Passwort-Richtlinie",
|
||||
"content": "# Passwort-Richtlinie\n\n...",
|
||||
"placeholders": ["{{COMPANY_NAME}}", "{{MIN_PASSWORD_LENGTH}}"],
|
||||
"language": "de",
|
||||
"jurisdiction": "DE"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend
|
||||
|
||||
Die Policy-Templates sind im **Dokumentengenerator** (`/sdk/document-generator`) unter 5 neuen Kategorie-Pills erreichbar:
|
||||
|
||||
| UI-Pill | Enthaltene Typen |
|
||||
|---------|-----------------|
|
||||
| IT-Sicherheit Policies | 14 Typen (information_security_policy bis vulnerability_management_policy) |
|
||||
| Daten-Policies | 5 Typen (data_protection_policy bis privacy_incident_policy) |
|
||||
| Personal-Policies | 5 Typen (employee_security_policy, security_awareness_policy, acceptable_use, remote_work_policy, offboarding_policy) |
|
||||
| Lieferanten-Policies | 3 Typen (vendor_risk_management_policy bis supplier_security_policy) |
|
||||
| BCM/Notfall | 3 Typen (business_continuity_policy bis crisis_management_policy) |
|
||||
|
||||
---
|
||||
|
||||
## Seeding
|
||||
|
||||
Das Seed-Script `backend-compliance/scripts/seed_policy_templates.py` fuegt alle 29 Templates ueber die API ein:
|
||||
|
||||
```bash
|
||||
python3 backend-compliance/scripts/seed_policy_templates.py
|
||||
```
|
||||
|
||||
Das Script nutzt die Production-API (`https://api-dev.breakpilot.ai`) und benoetigt einen gueltigen
|
||||
`X-Tenant-Id` Header. Bereits existierende Templates werden nicht dupliziert (Upsert ueber `document_type`).
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
- **48 Tests** in `test_policy_templates.py`
|
||||
- Alle 29 Dokumenttypen gegen `VALID_DOCUMENT_TYPES` validiert
|
||||
- API-Akzeptanz (POST 201) fuer jeden Typ (parametrized)
|
||||
- Filterung nach Kategorie (GET 200)
|
||||
- Platzhalter-Validierung
|
||||
- Seed-Script-Struktur (29 Templates, Pflichtfelder, deutsche Inhalte)
|
||||
- Ablehnung ungeltiger Typen (400)
|
||||
|
||||
---
|
||||
|
||||
## Zusammenspiel mit anderen Modulen
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Company Profile] --> B[Compliance Engine]
|
||||
B --> C[Policy-Bibliothek]
|
||||
C --> D[Dokumentengenerator]
|
||||
D --> E[Change-Requests]
|
||||
E --> F[Document Workflow]
|
||||
C --> G[Controls / Mapping-Matrix]
|
||||
G --> H[Process Manager Tasks]
|
||||
G --> I[Evidence Checks]
|
||||
```
|
||||
|
||||
Die Policy-Bibliothek bildet die **Vorlage-Ebene** der geplanten Compliance Engine:
|
||||
|
||||
1. **CompanyProfile + Scope** → bestimmt welche Regulations/Controls relevant sind
|
||||
2. **Controls** → verweisen auf relevante **Policies** aus der Bibliothek
|
||||
3. **Policies** → werden ueber den Dokumentengenerator als Entwuerfe erstellt
|
||||
4. **Entwuerfe** → durchlaufen den Document Workflow (Review → Freigabe → Veroeffentlichung)
|
||||
@@ -412,3 +412,8 @@ Die **Einwilligungen** legen fest, welche Datenpunkte einer Einwilligung beduerf
|
||||
Die **Rechtlichen Vorlagen** erstellen die zugehoerigen Dokumente (DSE, AGB, etc.).
|
||||
Der **Cookie Banner** konfiguriert das Frontend-Consent-Widget.
|
||||
Der **Document Workflow** fuhrt alle Dokumente durch den Freigabeprozess vor der Veroeffentlichung.
|
||||
|
||||
!!! info "Policy-Bibliothek (Migration 054)"
|
||||
Zusaetzlich zu den rechtlichen Texten stehen **29 deutsche Richtlinien-Templates** zur Verfuegung
|
||||
(IT-Sicherheit, Datenschutz, Personal, Lieferanten, BCM). Siehe
|
||||
[Policy-Bibliothek](policy-bibliothek.md) fuer Details.
|
||||
|
||||
@@ -100,6 +100,7 @@ nav:
|
||||
- Dokument-Versionierung: services/sdk-modules/versionierung.md
|
||||
- Change-Request System (CP-CR): services/sdk-modules/change-requests.md
|
||||
- Dokumentengenerierung: services/sdk-modules/dokumentengenerierung.md
|
||||
- Policy-Bibliothek (29 Richtlinien): services/sdk-modules/policy-bibliothek.md
|
||||
- Canonical Control Library (CP-CLIB): services/sdk-modules/canonical-control-library.md
|
||||
- Strategie:
|
||||
- Wettbewerbsanalyse & Roadmap: strategy/wettbewerbsanalyse.md
|
||||
|
||||
Reference in New Issue
Block a user