from __future__ import annotations import tempfile import unittest from pathlib import Path from govoplan_campaign.backend.campaign.models import CampaignConfig from govoplan_campaign.backend.attachments.resolver import resolve_entry_attachments from govoplan_campaign.backend.template_rendering import ( find_unresolved_placeholders, normalize_template_key, render_template, ) class CampaignTemplateRenderingTests(unittest.TestCase): def test_template_key_aliases_have_shared_normalization(self) -> None: cases = { " name ": "name", "fields.name": "name", "local.name": "local::name", "local:name": "local::name", "local::name": "local::name", "global.name": "global::name", "global:name": "global::name", "global::name": "global::name", } for raw, expected in cases.items(): with self.subTest(raw=raw): self.assertEqual(normalize_template_key(raw), expected) def test_rendering_preserves_both_syntaxes_none_and_missing_values(self) -> None: values = { "name": "Ada", "local::reference": 42, "global::empty": None, } template = "${fields.name}|{{ local.reference }}|${global:empty}|${missing}|{{ global.absent }}" self.assertEqual( render_template(template, values), "Ada|42||${missing}|{{ global.absent }}", ) self.assertEqual(render_template(template, values, keep_missing=False), "Ada|42|||") def test_rendering_unescapes_literal_dollar_placeholders(self) -> None: self.assertEqual( render_template(r"\${literal\}|${known}", {"known": "resolved"}, keep_missing=False), "${literal}|resolved", ) def test_unresolved_placeholders_are_normalized_and_deduplicated(self) -> None: unresolved = find_unresolved_placeholders( r"${fields.missing} {{ local:name }} ${global.other} {{local::name}} \${escaped\}" ) self.assertEqual(unresolved, {"missing", "local::name", "global::other"}) self.assertEqual(find_unresolved_placeholders(None), set()) def test_attachment_resolution_keeps_missing_placeholders(self) -> None: config = CampaignConfig.model_validate( { "version": "1.0", "campaign": {"id": "template-parity", "name": "Template parity", "mode": "test"}, "template": {"subject": "Subject", "text": "Body"}, "attachments": { "base_path": ".", "global": [ { "base_dir": ".", "file_filter": "${missing}.pdf", "required": False, "missing_behavior": "continue", } ], }, "entries": { "inline": [ { "id": "recipient-1", "to": [{"email": "recipient@example.test", "type": "to"}], } ] }, } ) with tempfile.TemporaryDirectory() as temp_dir: resolution = resolve_entry_attachments( config=config, campaign_file=Path(temp_dir) / "campaign.json", entry=config.entries.inline[0], # type: ignore[index] entry_index=1, ) self.assertEqual(resolution.attachments[0].file_filter, "${missing}.pdf") if __name__ == "__main__": unittest.main()