Files
breakpilot-compliance/backend-compliance/tests/test_legal_template_routes.py
Benjamin Admin cce2707c03
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 41s
CI/CD / test-python-backend-compliance (push) Successful in 31s
CI/CD / test-python-document-crawler (push) Successful in 21s
CI/CD / test-python-dsms-gateway (push) Successful in 16s
CI/CD / validate-canonical-controls (push) Successful in 10s
CI/CD / Deploy (push) Successful in 4s
fix: update 61 outdated test mocks to match current schemas
Tests were failing due to stale mock objects after schema extensions:
- DSFA: add _mapping property to _DictRow, use proper mock instead of MagicMock
- Company Profile: add 6 missing fields (project_id, offering_urls, etc.)
- Legal Templates/Policy: update document type count 52→58
- VVT: add 13 missing attributes to activity mock
- Legal Documents: align consent test assertions with production behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 06:40:42 +01:00

693 lines
29 KiB
Python

"""Tests for Legal Template routes and schemas (legal_template_routes.py)."""
import json
import pytest
from datetime import datetime
from unittest.mock import MagicMock, patch
from fastapi.testclient import TestClient
from fastapi import FastAPI
from compliance.api.legal_template_routes import (
LegalTemplateCreate,
LegalTemplateUpdate,
VALID_DOCUMENT_TYPES,
VALID_STATUSES,
router,
)
from compliance.api.db_utils import row_to_dict as _row_to_dict
DEFAULT_TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
from classroom_engine.database import get_db
app = FastAPI()
app.include_router(router)
DEFAULT_TENANT = DEFAULT_TENANT_ID
TEMPLATE_ID = "ffffffff-0001-0001-0001-000000000001"
UNKNOWN_ID = "aaaaaaaa-9999-9999-9999-999999999999"
DSE_PLACEHOLDERS = [
"{{COMPANY_NAME}}", "{{COMPANY_ADDRESS}}", "{{CONTACT_EMAIL}}", "{{VERSION_DATE}}"
]
# =============================================================================
# Helpers
# =============================================================================
def make_template_row(overrides=None):
data = {
"id": TEMPLATE_ID,
"tenant_id": DEFAULT_TENANT,
"document_type": "privacy_policy",
"title": "Datenschutzerklärung (DSGVO-konform)",
"description": "Vollständige DSE gemäß DSGVO Art. 13/14",
"content": "# Datenschutzerklärung\n\n## 1. Verantwortlicher\n\n{{COMPANY_NAME}}",
"placeholders": DSE_PLACEHOLDERS,
"language": "de",
"jurisdiction": "DE",
"license_id": "mit",
"license_name": "MIT License",
"source_name": "BreakPilot Compliance",
"attribution_required": False,
"is_complete_document": True,
"version": "1.0.0",
"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)
row = MagicMock()
row._mapping = data
return row
def make_db_mock():
db = MagicMock()
return db
# =============================================================================
# TestLegalTemplateSchemas
# =============================================================================
class TestLegalTemplateSchemas:
def test_create_schema_defaults(self):
"""LegalTemplateCreate sets sensible defaults."""
payload = LegalTemplateCreate(
document_type="privacy_policy",
title="Test DSE",
content="# Test"
)
assert payload.language == "de"
assert payload.jurisdiction == "DE"
assert payload.license_id == "mit"
assert payload.license_name == "MIT License"
assert payload.source_name == "BreakPilot Compliance"
assert payload.attribution_required is False
assert payload.is_complete_document is True
assert payload.version == "1.0.0"
assert payload.status == "published"
def test_create_schema_with_placeholders(self):
"""LegalTemplateCreate accepts placeholder list."""
payload = LegalTemplateCreate(
document_type="impressum",
title="Impressum",
content="# Impressum\n{{COMPANY_NAME}}",
placeholders=["{{COMPANY_NAME}}", "{{CEO_NAME}}"]
)
assert len(payload.placeholders) == 2
assert "{{COMPANY_NAME}}" in payload.placeholders
def test_update_schema_all_optional(self):
"""LegalTemplateUpdate: all fields optional."""
payload = LegalTemplateUpdate()
d = payload.model_dump(exclude_unset=True)
assert d == {}
def test_update_schema_partial(self):
"""LegalTemplateUpdate partial: only set fields serialized."""
payload = LegalTemplateUpdate(status="archived", title="Neue DSE")
d = payload.model_dump(exclude_unset=True)
assert d == {"status": "archived", "title": "Neue DSE"}
def test_valid_document_types_constant(self):
"""VALID_DOCUMENT_TYPES contains all 58 expected types (Migration 020+051+054+056+073)."""
# Original types
assert "privacy_policy" in VALID_DOCUMENT_TYPES
assert "terms_of_service" in VALID_DOCUMENT_TYPES
assert "impressum" in VALID_DOCUMENT_TYPES
assert "cookie_policy" in VALID_DOCUMENT_TYPES
# Renamed types (Migration 020)
assert "dpa" in VALID_DOCUMENT_TYPES
assert "widerruf" in VALID_DOCUMENT_TYPES
# New types (Migration 020)
assert "nda" in VALID_DOCUMENT_TYPES
assert "sla" in VALID_DOCUMENT_TYPES
assert "acceptable_use" in VALID_DOCUMENT_TYPES
assert "community_guidelines" in VALID_DOCUMENT_TYPES
assert "copyright_policy" in VALID_DOCUMENT_TYPES
assert "cloud_service_agreement" in VALID_DOCUMENT_TYPES
assert "data_usage_clause" in VALID_DOCUMENT_TYPES
assert "cookie_banner" in VALID_DOCUMENT_TYPES
assert "agb" in VALID_DOCUMENT_TYPES
assert "clause" in VALID_DOCUMENT_TYPES
# 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
# CRA Cybersecurity (Migration 056)
assert "cybersecurity_policy" in VALID_DOCUMENT_TYPES
# DSFA template
assert "dsfa" in VALID_DOCUMENT_TYPES
# Module document templates (Migration 073)
assert "vvt_register" in VALID_DOCUMENT_TYPES
assert "tom_documentation" in VALID_DOCUMENT_TYPES
assert "loeschkonzept" in VALID_DOCUMENT_TYPES
assert "pflichtenregister" in VALID_DOCUMENT_TYPES
# Total: 16 original + 7 security concepts + 29 policies + 1 CRA + 1 DSFA + 4 module docs = 58
assert len(VALID_DOCUMENT_TYPES) == 58
# 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
def test_create_schema_attribution_fields(self):
"""LegalTemplateCreate accepts attribution fields (uses dpa, post-Migration 020)."""
payload = LegalTemplateCreate(
document_type="dpa",
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."""
assert "published" in VALID_STATUSES
assert "draft" in VALID_STATUSES
assert "archived" in VALID_STATUSES
# =============================================================================
# TestLegalTemplateDB
# =============================================================================
class TestLegalTemplateDB:
def test_row_to_dict_converts_datetime(self):
"""_row_to_dict converts datetime to ISO string."""
row = make_template_row()
result = _row_to_dict(row)
assert result["created_at"] == "2024-01-01T00:00:00"
assert result["updated_at"] == "2024-01-01T00:00:00"
def test_row_to_dict_preserves_strings(self):
"""_row_to_dict preserves string fields unchanged."""
row = make_template_row()
result = _row_to_dict(row)
assert result["title"] == "Datenschutzerklärung (DSGVO-konform)"
assert result["license_id"] == "mit"
assert result["document_type"] == "privacy_policy"
def test_row_to_dict_preserves_list(self):
"""_row_to_dict preserves list fields (placeholders)."""
row = make_template_row()
result = _row_to_dict(row)
assert isinstance(result["placeholders"], list)
assert "{{COMPANY_NAME}}" in result["placeholders"]
# =============================================================================
# TestLegalTemplateSearch
# =============================================================================
class TestLegalTemplateSearch:
def setup_method(self):
self.db = make_db_mock()
app.dependency_overrides[get_db] = lambda: self.db
self.client = TestClient(app)
def teardown_method(self):
app.dependency_overrides.clear()
def _setup_list(self, rows, total=None):
count_row = MagicMock()
count_row.__getitem__ = lambda self, i: total if total is not None else len(rows)
self.db.execute.return_value.fetchone.side_effect = [count_row]
self.db.execute.return_value.fetchall.return_value = rows
def test_list_returns_200(self):
"""GET /legal-templates returns 200 with templates list."""
rows = [make_template_row()]
count_mock = MagicMock()
count_mock.__getitem__ = lambda self, i: 1
execute_results = []
first_call = MagicMock()
first_call.fetchone.return_value = count_mock
second_call = MagicMock()
second_call.fetchall.return_value = rows
self.db.execute.side_effect = [first_call, second_call]
res = self.client.get("/legal-templates")
assert res.status_code == 200
data = res.json()
assert "templates" in data
assert "total" in data
def test_list_filter_by_document_type(self):
"""GET /legal-templates?document_type=impressum passes filter."""
count_mock = MagicMock()
count_mock.__getitem__ = lambda self, i: 0
first_call = MagicMock()
first_call.fetchone.return_value = count_mock
second_call = MagicMock()
second_call.fetchall.return_value = []
self.db.execute.side_effect = [first_call, second_call]
res = self.client.get("/legal-templates?document_type=impressum")
assert res.status_code == 200
# Verify the SQL call used document_type filter
call_args = self.db.execute.call_args_list[0]
sql_str = str(call_args[0][0])
assert "document_type" in sql_str
def test_create_invalid_document_type_returns_400(self):
"""POST /legal-templates with invalid document_type returns 400."""
res = self.client.post("/legal-templates", json={
"document_type": "unknown_type",
"title": "Test",
"content": "# Test"
})
assert res.status_code == 400
assert "document_type" in res.json()["detail"]
def test_create_invalid_status_returns_400(self):
"""POST /legal-templates with invalid status returns 400."""
res = self.client.post("/legal-templates", json={
"document_type": "privacy_policy",
"title": "Test",
"content": "# Test",
"status": "invalid_status"
})
assert res.status_code == 400
def test_get_nonexistent_returns_404(self):
"""GET /legal-templates/{id} for unknown ID returns 404."""
self.db.execute.return_value.fetchone.return_value = None
res = self.client.get(f"/legal-templates/{UNKNOWN_ID}")
assert res.status_code == 404
def test_update_no_fields_returns_400(self):
"""PUT /legal-templates/{id} with empty body returns 400."""
res = self.client.put(f"/legal-templates/{TEMPLATE_ID}", json={})
assert res.status_code == 400
assert "No fields" in res.json()["detail"]
def test_delete_nonexistent_returns_404(self):
"""DELETE /legal-templates/{id} for unknown ID returns 404."""
result_mock = MagicMock()
result_mock.rowcount = 0
self.db.execute.return_value = result_mock
res = self.client.delete(f"/legal-templates/{UNKNOWN_ID}")
assert res.status_code == 404
def test_status_endpoint_returns_200(self):
"""GET /legal-templates/status returns dynamic count structure."""
# Mock 3 sequential execute calls: total, by_status, by_type
total_mock = MagicMock()
total_mock.__getitem__ = lambda self, i: 20
status_rows = [
MagicMock(**{"__getitem__": lambda s, i: ["published", 18][i]}),
MagicMock(**{"__getitem__": lambda s, i: ["draft", 2][i]}),
]
type_rows = [
MagicMock(**{"__getitem__": lambda s, i: ["privacy_policy", 2][i]}),
MagicMock(**{"__getitem__": lambda s, i: ["dpa", 2][i]}),
MagicMock(**{"__getitem__": lambda s, i: ["nda", 2][i]}),
MagicMock(**{"__getitem__": lambda s, i: ["widerruf", 1][i]}),
]
call1 = MagicMock(); call1.fetchone.return_value = total_mock
call2 = MagicMock(); call2.fetchall.return_value = status_rows
call3 = MagicMock(); call3.fetchall.return_value = type_rows
self.db.execute.side_effect = [call1, call2, call3]
res = self.client.get("/legal-templates/status")
assert res.status_code == 200
data = res.json()
assert "total" in data
assert "by_type" in data
assert "by_status" in data
assert "dpa" in data["by_type"]
assert "nda" in data["by_type"]
# Old names must not appear (they were renamed)
assert "data_processing_agreement" not in data["by_type"]
assert "withdrawal_policy" not in data["by_type"]
def test_sources_endpoint_returns_list(self):
"""GET /legal-templates/sources returns sources list."""
self.db.execute.return_value.fetchall.return_value = [
("BreakPilot Compliance",),
]
res = self.client.get("/legal-templates/sources")
assert res.status_code == 200
data = res.json()
assert "sources" in data
assert "BreakPilot Compliance" in data["sources"]
# =============================================================================
# TestLegalTemplateSeed
# =============================================================================
class TestLegalTemplateSeed:
"""Validate that seed template structures are correct."""
def test_dse_placeholders_present(self):
"""DSE template row has expected placeholder tokens."""
row = make_template_row({
"document_type": "privacy_policy",
"placeholders": [
"{{COMPANY_NAME}}", "{{COMPANY_ADDRESS}}", "{{COMPANY_CITY}}",
"{{CONTACT_EMAIL}}", "{{DPO_NAME}}", "{{DPO_EMAIL}}",
"{{SUPERVISORY_AUTHORITY}}", "{{VERSION_DATE}}"
]
})
result = _row_to_dict(row)
assert "{{COMPANY_NAME}}" in result["placeholders"]
assert "{{CONTACT_EMAIL}}" in result["placeholders"]
assert "{{VERSION_DATE}}" in result["placeholders"]
assert len(result["placeholders"]) == 8
def test_impressum_has_ceo_placeholder(self):
"""Impressum template row has CEO_NAME placeholder."""
row = make_template_row({
"document_type": "impressum",
"placeholders": [
"{{COMPANY_NAME}}", "{{COMPANY_LEGAL_FORM}}", "{{COMPANY_ADDRESS}}",
"{{COMPANY_CITY}}", "{{CEO_NAME}}", "{{COMPANY_PHONE}}",
"{{CONTACT_EMAIL}}", "{{WEBSITE_URL}}", "{{REGISTER_SECTION}}", "{{VAT_SECTION}}"
]
})
result = _row_to_dict(row)
assert "{{CEO_NAME}}" in result["placeholders"]
assert "{{WEBSITE_URL}}" in result["placeholders"]
def test_agb_has_pricing_placeholder(self):
"""AGB template row has PRICING_SECTION placeholder."""
row = make_template_row({
"document_type": "terms_of_service",
"placeholders": [
"{{COMPANY_NAME}}", "{{SERVICE_DESCRIPTION}}", "{{PRICING_SECTION}}",
"{{PAYMENT_TERMS}}", "{{COMPANY_CITY}}", "{{PRIVACY_POLICY_URL}}", "{{VERSION_DATE}}"
]
})
result = _row_to_dict(row)
assert "{{PRICING_SECTION}}" in result["placeholders"]
assert "{{PAYMENT_TERMS}}" in result["placeholders"]
assert "{{PRIVACY_POLICY_URL}}" in result["placeholders"]
def test_all_seeds_use_mit_license(self):
"""All seed templates declare MIT license and no attribution required."""
for doc_type in ["privacy_policy", "terms_of_service", "impressum"]:
row = make_template_row({"document_type": doc_type})
result = _row_to_dict(row)
assert result["license_id"] == "mit"
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": "dpa",
"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": "widerruf",
"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"] == "widerruf"
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_widerruf_type_valid(self):
"""widerruf is accepted as valid document_type (renamed from withdrawal_policy)."""
assert "widerruf" in VALID_DOCUMENT_TYPES
assert "withdrawal_policy" not in VALID_DOCUMENT_TYPES
def test_dpa_type_valid(self):
"""dpa is accepted as valid document_type (renamed from data_processing_agreement)."""
assert "dpa" in VALID_DOCUMENT_TYPES
assert "data_processing_agreement" not in VALID_DOCUMENT_TYPES
# =============================================================================
# TestLegalTemplateNewTypes
# =============================================================================
class TestLegalTemplateNewTypes:
"""Validate new document types added in Migration 020."""
def test_all_58_types_present(self):
"""VALID_DOCUMENT_TYPES has exactly 58 entries (16 + 7 security + 29 policies + 1 CRA + 1 DSFA + 4 module docs)."""
assert len(VALID_DOCUMENT_TYPES) == 58
def test_new_types_are_valid(self):
"""All Migration 020 new types are accepted."""
new_types = ["nda", "sla", "acceptable_use", "community_guidelines",
"copyright_policy", "cloud_service_agreement",
"data_usage_clause", "cookie_banner", "agb", "clause"]
for t in new_types:
assert t in VALID_DOCUMENT_TYPES, f"Type '{t}' missing from VALID_DOCUMENT_TYPES"
def test_nda_template_has_parties_placeholders(self):
"""NDA template has DISCLOSING_PARTY and RECEIVING_PARTY placeholders."""
row = make_template_row({
"document_type": "nda",
"title": "Geheimhaltungsvereinbarung (NDA) — DE",
"language": "de",
"placeholders": [
"{{DISCLOSING_PARTY}}", "{{RECEIVING_PARTY}}", "{{PURPOSE}}",
"{{EFFECTIVE_DATE}}", "{{DURATION_YEARS}}", "{{PENALTY_AMOUNT}}",
"{{JURISDICTION_CITY}}"
],
})
result = _row_to_dict(row)
assert result["document_type"] == "nda"
assert "{{DISCLOSING_PARTY}}" in result["placeholders"]
assert "{{RECEIVING_PARTY}}" in result["placeholders"]
assert "{{PURPOSE}}" in result["placeholders"]
def test_nda_en_template_is_valid(self):
"""NDA EN template has EU jurisdiction and English language."""
row = make_template_row({
"document_type": "nda",
"title": "Non-Disclosure Agreement (NDA) — EN (EU)",
"language": "en",
"jurisdiction": "EU",
})
result = _row_to_dict(row)
assert result["document_type"] == "nda"
assert result["language"] == "en"
assert result["jurisdiction"] == "EU"
def test_sla_template_has_availability_placeholder(self):
"""SLA template includes AVAILABILITY_PERCENT placeholder."""
row = make_template_row({
"document_type": "sla",
"placeholders": [
"{{SERVICE_PROVIDER}}", "{{CUSTOMER}}", "{{SERVICE_NAME}}",
"{{AVAILABILITY_PERCENT}}", "{{SUPPORT_EMAIL}}", "{{JURISDICTION_CITY}}"
],
})
result = _row_to_dict(row)
assert result["document_type"] == "sla"
assert "{{AVAILABILITY_PERCENT}}" in result["placeholders"]
def test_acceptable_use_type_valid(self):
"""acceptable_use is a valid document type."""
assert "acceptable_use" in VALID_DOCUMENT_TYPES
def test_acceptable_use_template_structure(self):
"""AUP template has required placeholders."""
row = make_template_row({
"document_type": "acceptable_use",
"language": "en",
"jurisdiction": "EU",
"placeholders": [
"{{COMPANY_NAME}}", "{{SERVICE_NAME}}", "{{EFFECTIVE_DATE}}",
"{{ABUSE_EMAIL}}", "{{COMPANY_ADDRESS}}", "{{CONTACT_EMAIL}}"
],
})
result = _row_to_dict(row)
assert result["document_type"] == "acceptable_use"
assert "{{ABUSE_EMAIL}}" in result["placeholders"]
def test_community_guidelines_type_valid(self):
"""community_guidelines is a valid document type."""
assert "community_guidelines" in VALID_DOCUMENT_TYPES
def test_agb_type_valid(self):
"""agb is a valid document type (German AGB, distinct from terms_of_service)."""
assert "agb" in VALID_DOCUMENT_TYPES
def test_agb_template_has_pricing_and_termination(self):
"""AGB template has PRICING_SECTION and TERMINATION_NOTICE_DAYS placeholders."""
row = make_template_row({
"document_type": "agb",
"placeholders": [
"{{COMPANY_NAME}}", "{{COMPANY_ADDRESS}}", "{{VERSION_DATE}}",
"{{SERVICE_NAME}}", "{{SERVICE_DESCRIPTION}}", "{{PRICING_SECTION}}",
"{{PAYMENT_DAYS}}", "{{PRIVACY_POLICY_URL}}",
"{{TERMINATION_NOTICE_DAYS}}", "{{JURISDICTION_CITY}}"
],
})
result = _row_to_dict(row)
assert result["document_type"] == "agb"
assert "{{PRICING_SECTION}}" in result["placeholders"]
assert "{{TERMINATION_NOTICE_DAYS}}" in result["placeholders"]
def test_clause_type_is_modular(self):
"""clause document type can be a non-complete document (is_complete_document=False)."""
row = make_template_row({
"document_type": "clause",
"is_complete_document": False,
"language": "en",
})
result = _row_to_dict(row)
assert result["document_type"] == "clause"
assert result["is_complete_document"] is False
def test_cloud_service_agreement_type_valid(self):
"""cloud_service_agreement is a valid document type."""
assert "cloud_service_agreement" in VALID_DOCUMENT_TYPES
def test_data_usage_clause_is_non_complete(self):
"""data_usage_clause is typically a clause (non-complete document)."""
assert "data_usage_clause" in VALID_DOCUMENT_TYPES
def test_cookie_banner_type_valid(self):
"""cookie_banner is a valid document type."""
assert "cookie_banner" in VALID_DOCUMENT_TYPES
def test_copyright_policy_type_valid(self):
"""copyright_policy is a valid document type."""
assert "copyright_policy" in VALID_DOCUMENT_TYPES
def test_create_nda_with_new_type(self):
"""POST /legal-templates accepts nda as document_type."""
from fastapi.testclient import TestClient
db = MagicMock()
app2 = __import__('fastapi', fromlist=['FastAPI']).FastAPI()
app2.include_router(router)
from classroom_engine.database import get_db
app2.dependency_overrides[get_db] = lambda: db
row = make_template_row({"document_type": "nda", "title": "NDA Test"})
db.execute.return_value.fetchone.return_value = row
db.commit = MagicMock()
client = TestClient(app2)
res = client.post("/legal-templates", json={
"document_type": "nda",
"title": "NDA Test",
"content": "# NDA\n{{DISCLOSING_PARTY}}"
})
assert res.status_code == 201
def test_create_with_old_dpa_name_rejected(self):
"""POST /legal-templates rejects data_processing_agreement (old name)."""
from fastapi.testclient import TestClient
db = MagicMock()
app2 = __import__('fastapi', fromlist=['FastAPI']).FastAPI()
app2.include_router(router)
from classroom_engine.database import get_db
app2.dependency_overrides[get_db] = lambda: db
client = TestClient(app2)
res = client.post("/legal-templates", json={
"document_type": "data_processing_agreement",
"title": "AVV Old Name",
"content": "# AVV"
})
assert res.status_code == 400
def test_create_with_old_withdrawal_policy_name_rejected(self):
"""POST /legal-templates rejects withdrawal_policy (old name)."""
from fastapi.testclient import TestClient
db = MagicMock()
app2 = __import__('fastapi', fromlist=['FastAPI']).FastAPI()
app2.include_router(router)
from classroom_engine.database import get_db
app2.dependency_overrides[get_db] = lambda: db
client = TestClient(app2)
res = client.post("/legal-templates", json={
"document_type": "withdrawal_policy",
"title": "Widerruf Old Name",
"content": "# Widerruf"
})
assert res.status_code == 400