from __future__ import annotations import json import unittest from datetime import UTC, datetime from sqlalchemy import create_engine from sqlalchemy.orm import Session from govoplan_core.core.dsar import DsarProvider, DsarSubjectRef from govoplan_core.db.base import Base from govoplan_templates.backend.db.models import ( TemplateDefinition, TemplateRender, TemplateRevision, ) from govoplan_templates.backend.dsar_provider import ( TEMPLATES_DSAR_CAPABILITY, TemplatesDsarProvider, ) from govoplan_templates.backend.manifest import manifest NOW = datetime(2026, 8, 22, 14, 0, tzinfo=UTC) class TemplatesDsarProviderTests(unittest.TestCase): def setUp(self) -> None: self.engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(self.engine) self.session = Session(self.engine) self.provider = TemplatesDsarProvider() self._seed() self.session.commit() def tearDown(self) -> None: self.session.close() self.engine.dispose() def _seed(self) -> None: self.session.add( TemplateDefinition( id="template-1", tenant_id="tenant-1", scope_type="tenant", name="Sensitive template title do not export", slug="secret-slug-do-not-export", description="description-do-not-export", template_type="letter", status="published", current_revision_id="revision-1", current_revision=1, published_revision_id="revision-1", resource_revision=2, created_by_account_id="account-1", updated_by_account_id="account-1", metadata_={"secret": "definition-metadata-do-not-export"}, created_at=NOW, updated_at=NOW, ) ) self.session.add( TemplateRevision( id="revision-1", tenant_id="tenant-1", template_id="template-1", revision=1, definition_hash="definition-hash-do-not-export", template_type="letter", usages=["campaign"], locale="de", required_fields=[{"secret": "required-field-do-not-export"}], output_profiles=[{"secret": "output-profile-do-not-export"}], content_text="template-text-do-not-export", content_html="template-html-do-not-export", layout={"secret": "layout-do-not-export"}, metadata_={"secret": "revision-metadata-do-not-export"}, created_by_account_id="account-1", published_at=NOW, published_by_account_id="account-1", created_at=NOW, updated_at=NOW, ) ) self.session.add( TemplateRender( id="render-1", tenant_id="tenant-1", template_id="template-1", revision_id="revision-1", revision_number=1, mode="final", usage="campaign", output_format="html", content_type="text/html", filename="personal-filename-do-not-export.html", idempotency_key="render-idempotency-do-not-export", template_hash="template-hash-do-not-export", input_hash="input-hash-do-not-export", renderer_version="renderer-v1", output_sha256="output-hash-do-not-export", output_size_bytes=123, item_count=2, page_count=1, diagnostics=[{"secret": "diagnostic-do-not-export"}], input_snapshot={"person": "input-person-do-not-export"}, artifact_ref={"secret": "artifact-ref-do-not-export"}, payload=b"output-payload-do-not-export", created_by_account_id="account-1", created_at=NOW, updated_at=NOW, ) ) def test_search_is_minimized_and_narrowable(self) -> None: self.assertIsInstance(self.provider, DsarProvider) records = self.provider.search_subject( self.session, tenant_id="tenant-1", subject=DsarSubjectRef(account_id="account-1"), ) self.assertEqual(3, len(records)) exported = json.dumps([record.to_dict() for record in records]) for excluded in ( "Sensitive template title do not export", "secret-slug-do-not-export", "definition-metadata-do-not-export", "definition-hash-do-not-export", "required-field-do-not-export", "template-text-do-not-export", "template-html-do-not-export", "personal-filename-do-not-export", "render-idempotency-do-not-export", "template-hash-do-not-export", "input-person-do-not-export", "artifact-ref-do-not-export", "output-payload-do-not-export", ): self.assertNotIn(excluded, exported) narrowed = self.provider.search_subject( self.session, tenant_id="tenant-1", subject=DsarSubjectRef( account_id="account-1", external_references={"templates.render": "render-1"}, ), ) self.assertEqual( {"template_render_actor_attribution"}, {record.resource_type for record in narrowed}, ) def test_account_is_required_and_records_are_retained(self) -> None: self.assertEqual( (), self.provider.search_subject( self.session, tenant_id="tenant-1", subject=DsarSubjectRef(email="author@example.test"), ), ) subject = DsarSubjectRef(account_id="account-1") records = self.provider.search_subject( self.session, tenant_id="tenant-1", subject=subject ) actions = self.provider.plan_erasure( self.session, tenant_id="tenant-1", subject=subject, records=records, ) self.assertTrue(all(action.kind == "retain" for action in actions)) def test_manifest_registers_provider_and_documentation(self) -> None: self.assertIn(TEMPLATES_DSAR_CAPABILITY, manifest.capability_factories) self.assertIn( "templates.data-subject-requests", {topic.id for topic in manifest.documentation}, ) if __name__ == "__main__": unittest.main()