"""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