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
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user