feat: Legal Templates — Attribution-Tracking + 6 neue Templates (DE/EN)
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 47s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 18s

Migration 019: 5 neue Herkunftsspalten (source_url, source_repo,
source_file_path, source_retrieved_at, attribution_text, inspiration_sources)
ermöglichen lückenlosen Nachweis jeder Template-Quelle.

Neue Templates:
  DE: AVV (Art. 28 DSGVO), Widerrufsbelehrung (EGBGB Anlage 1, §5 UrhG),
      Cookie-Richtlinie (TTDSG §25)
  EN: Privacy Policy (GDPR), Terms of Service (EU Directive 2011/83),
      Data Processing Agreement (GDPR Art. 28)

Gesamt: 9 Templates — 5 DE, 4 EN | 6 document_type-Werte

- VALID_DOCUMENT_TYPES um 3 neue Typen erweitert
- Create/Update-Schemas: attribution fields ergänzt
- Status-Endpoint: alle 6 Typen in by_type
- Tests: 34/34 — alle grün

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-04 09:00:25 +01:00
parent f909182632
commit d454acceff
4 changed files with 906 additions and 17 deletions

View File

@@ -55,6 +55,13 @@ def make_template_row(overrides=None):
"status": "published",
"created_at": datetime(2024, 1, 1),
"updated_at": datetime(2024, 1, 1),
# Attribution columns (Migration 019)
"source_url": None,
"source_repo": None,
"source_file_path": None,
"source_retrieved_at": None,
"attribution_text": "Self-authored by BreakPilot Compliance (MIT License, 2026-03-04).",
"inspiration_sources": [{"source": "Self-authored", "license": "MIT"}],
}
if overrides:
data.update(overrides)
@@ -114,11 +121,39 @@ class TestLegalTemplateSchemas:
assert d == {"status": "archived", "title": "Neue DSE"}
def test_valid_document_types_constant(self):
"""VALID_DOCUMENT_TYPES contains the 3 expected types."""
"""VALID_DOCUMENT_TYPES contains all 6 expected types."""
assert "privacy_policy" in VALID_DOCUMENT_TYPES
assert "terms_of_service" in VALID_DOCUMENT_TYPES
assert "impressum" in VALID_DOCUMENT_TYPES
assert len(VALID_DOCUMENT_TYPES) == 3
assert "data_processing_agreement" in VALID_DOCUMENT_TYPES
assert "withdrawal_policy" in VALID_DOCUMENT_TYPES
assert "cookie_policy" in VALID_DOCUMENT_TYPES
assert len(VALID_DOCUMENT_TYPES) == 6
def test_create_schema_attribution_fields(self):
"""LegalTemplateCreate accepts attribution fields."""
payload = LegalTemplateCreate(
document_type="data_processing_agreement",
title="AVV Test",
content="# AVV\n{{CONTROLLER_NAME}}",
source_url="https://github.com/example/legal",
source_repo="https://github.com/example/legal",
source_file_path="dpa-de.md",
attribution_text="Self-authored MIT.",
inspiration_sources=[{"source": "test", "license": "MIT"}],
)
assert payload.source_url == "https://github.com/example/legal"
assert payload.attribution_text == "Self-authored MIT."
assert len(payload.inspiration_sources) == 1
def test_update_schema_inspiration_sources(self):
"""LegalTemplateUpdate accepts inspiration_sources list."""
payload = LegalTemplateUpdate(
inspiration_sources=[{"source": "Self-authored", "license": "MIT"}]
)
d = payload.model_dump(exclude_unset=True)
assert "inspiration_sources" in d
assert d["inspiration_sources"][0]["license"] == "MIT"
def test_valid_statuses_constant(self):
"""VALID_STATUSES contains expected values."""
@@ -267,19 +302,23 @@ class TestLegalTemplateSearch:
assert res.status_code == 404
def test_status_endpoint_returns_200(self):
"""GET /legal-templates/status returns count structure."""
"""GET /legal-templates/status returns count structure with all 6 types."""
row = MagicMock()
row._mapping = {
"total": 3, "published": 3, "draft": 0, "archived": 0,
"privacy_policy": 1, "terms_of_service": 1, "impressum": 1
"total": 9, "published": 9, "draft": 0, "archived": 0,
"privacy_policy": 2, "terms_of_service": 2, "impressum": 1,
"data_processing_agreement": 2, "withdrawal_policy": 1, "cookie_policy": 1,
}
self.db.execute.return_value.fetchone.return_value = row
res = self.client.get("/legal-templates/status")
assert res.status_code == 200
data = res.json()
assert data["total"] == 3
assert data["total"] == 9
assert "by_type" in data
assert "by_status" in data
assert "data_processing_agreement" in data["by_type"]
assert "withdrawal_policy" in data["by_type"]
assert "cookie_policy" in data["by_type"]
def test_sources_endpoint_returns_list(self):
"""GET /legal-templates/sources returns sources list."""
@@ -353,3 +392,73 @@ class TestLegalTemplateSeed:
assert result["license_name"] == "MIT License"
assert result["attribution_required"] is False
assert result["is_complete_document"] is True
def test_dpa_template_has_controller_processor_placeholders(self):
"""AVV/DPA template has CONTROLLER_NAME and PROCESSOR_NAME placeholders."""
row = make_template_row({
"document_type": "data_processing_agreement",
"placeholders": [
"{{CONTROLLER_NAME}}", "{{CONTROLLER_ADDRESS}}", "{{CONTROLLER_CITY}}",
"{{PROCESSOR_NAME}}", "{{PROCESSOR_ADDRESS}}", "{{PROCESSOR_CITY}}",
"{{SERVICE_DESCRIPTION}}", "{{DATA_CATEGORIES}}", "{{DATA_SUBJECTS}}",
"{{CONTRACT_DATE}}", "{{SUPERVISORY_AUTHORITY}}"
]
})
result = _row_to_dict(row)
assert "{{CONTROLLER_NAME}}" in result["placeholders"]
assert "{{PROCESSOR_NAME}}" in result["placeholders"]
assert "{{DATA_CATEGORIES}}" in result["placeholders"]
def test_withdrawal_policy_has_public_domain_license(self):
"""Widerrufsbelehrung uses Public Domain license (based on §5 UrhG source)."""
row = make_template_row({
"document_type": "withdrawal_policy",
"license_id": "public_domain",
"license_name": "Public Domain / Gemeinfrei",
"source_url": "https://www.gesetze-im-internet.de/egbgb/anlage_1.html",
"attribution_text": "Basiert auf der amtlichen Musterwiderrufsbelehrung.",
"inspiration_sources": [{"source": "EGBGB Anlage 1", "license": "Public Domain gemäß §5 UrhG"}],
})
result = _row_to_dict(row)
assert result["document_type"] == "withdrawal_policy"
assert result["license_id"] == "public_domain"
assert result["source_url"] == "https://www.gesetze-im-internet.de/egbgb/anlage_1.html"
assert result["attribution_text"] is not None
def test_attribution_text_stored_in_row(self):
"""attribution_text and inspiration_sources are returned in _row_to_dict."""
row = make_template_row({
"attribution_text": "Self-authored by BreakPilot Compliance (MIT License, 2026-03-04).",
"inspiration_sources": [{"source": "Self-authored", "license": "MIT"}],
"source_url": None,
"source_repo": None,
"source_file_path": None,
"source_retrieved_at": None,
})
result = _row_to_dict(row)
assert "Self-authored" in result["attribution_text"]
assert result["inspiration_sources"][0]["license"] == "MIT"
def test_english_privacy_policy_eu_jurisdiction(self):
"""EN Privacy Policy template uses EU jurisdiction."""
row = make_template_row({
"document_type": "privacy_policy",
"language": "en",
"jurisdiction": "EU",
"title": "Privacy Policy (GDPR-compliant, EU)",
})
result = _row_to_dict(row)
assert result["language"] == "en"
assert result["jurisdiction"] == "EU"
def test_cookie_policy_type_valid(self):
"""cookie_policy is accepted as valid document_type."""
assert "cookie_policy" in VALID_DOCUMENT_TYPES
def test_withdrawal_policy_type_valid(self):
"""withdrawal_policy is accepted as valid document_type."""
assert "withdrawal_policy" in VALID_DOCUMENT_TYPES
def test_dpa_type_valid(self):
"""data_processing_agreement is accepted as valid document_type."""
assert "data_processing_agreement" in VALID_DOCUMENT_TYPES