work in progress on MarkdownTemplate interface

This commit is contained in:
2026-10-06 16:34:54 +02:00
parent 8d8acd052e
commit 7f37cebc40
9 changed files with 170 additions and 13 deletions

View File

@ -29,6 +29,8 @@ from .validation import (
ValidAddressList,
)
InvalidAddress = InvalidAddressError
logger = logging.getLogger(__name__)
@ -72,6 +74,44 @@ class MarkdownTemplate:
self.raw_content = self.load_raw_template(template_name)
self.metadata, self.md_body = self.parse_template(self.raw_content)
self.validate()
self._from_email = self._init_from_email()
self._to = self._init_address_list("to")
self._cc = self._init_address_list("cc")
self._bcc = self._init_address_list("bcc")
self._reply_to = self._init_address_list("reply_to")
def _init_from_email(self) -> ValidAddress | None:
raw_from = self.metadata.get("from_email")
if raw_from:
return ValidAddress.factory(raw_from)
return None
def _init_address_list(self, key: str) -> ValidAddressList:
raw_val = self.metadata.get(key)
if not raw_val:
return ValidAddressList()
items = [raw_val] if isinstance(raw_val, str) else list(raw_val)
return ValidAddressList(items)
@property
def from_email(self) -> ValidAddress | None:
return self._from_email
@property
def to(self) -> ValidAddressList:
return self._to
@property
def cc(self) -> ValidAddressList:
return self._cc
@property
def bcc(self) -> ValidAddressList:
return self._bcc
@property
def reply_to(self) -> ValidAddressList:
return self._reply_to
@classmethod
def load_raw_template(cls, template_name: str) -> str:

View File

@ -6,6 +6,6 @@
<style>{% block style %}{% endblock %}</style>
</head>
<body>
{% block body %}{{ content_html|safe }}{% endblock %}
{% block body %}{{ content_html|safe }}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,5 @@
---
subject: "Test from_email invalide"
from_email: "invalid-email-address"
---
Contenu de test.

View File

@ -0,0 +1,7 @@
---
subject: "Test adresse invalide dans to"
to:
- "valid@example.org"
- "bad address"
---
Contenu de test.

View File

@ -0,0 +1,4 @@
---
to: "test@example.com"
---
Corps de test sans sujet.

View File

@ -1,5 +0,0 @@
---
subject: "Notification pour {{ missing_user }}"
to: "test@example.com"
---
Corps de test.

View File

@ -0,0 +1,11 @@
---
subject: "Test adresses"
to:
- "Victor Hugo <hugo@les-miserables.fr>"
- "antoine@saint-exupery.aero"
cc: "Aimé Césaire <cesaire@cahiers.net>"
bcc:
- "boris@vian.jazz"
reply_to: "contact@example.org"
---
Contenu de test pour les adresses.

View File

@ -0,0 +1,5 @@
---
subject: "Test from_email valide"
from_email: "Victor Hugo <hugo@les-miserables.fr>"
---
Contenu de test.

View File

@ -1,16 +1,106 @@
import unittest
from django.template.exceptions import TemplateDoesNotExist
from django.test import SimpleTestCase
from django_courriel.md import MarkdownTemplate
from django_courriel.validation import (
InvalidAddressError,
InvalidFrontMatter,
ValidAddress,
ValidAddressList,
)
class MarkdownTemplateTest(SimpleTestCase):
def test_init(self):
raise NotImplementedError()
def test_init_valid(self):
"""A valid markdown template initializes with metadata and markdown body."""
template = MarkdownTemplate("minimal.md")
self.assertEqual(template.template_name, "minimal.md")
self.assertIn("subject", template.metadata)
self.assertEqual(template.metadata["subject"], "Hello")
self.assertIn("Hello world !", template.md_body)
def test_minimal(self):
raise NotImplementedError()
def test_init_template_does_not_exist(self):
"""Instantiating with an unresolvable template raises TemplateDoesNotExist."""
with self.assertRaises(TemplateDoesNotExist):
MarkdownTemplate("does_not_exist.md")
def test_init_missing_front_matter(self):
"""A template without frontmatter delimiters raises InvalidFrontMatter."""
with self.assertRaises(InvalidFrontMatter):
MarkdownTemplate("missing_front_matter.md")
def test_init_invalid_front_matter(self):
"""A template with malformed frontmatter raises InvalidFrontMatter."""
with self.assertRaises(InvalidFrontMatter):
MarkdownTemplate("invalid_front_matter.md")
def test_init_missing_subject(self):
"""A template frontmatter without a 'subject' key raises InvalidFrontMatter."""
with self.assertRaises(InvalidFrontMatter) as ctx:
MarkdownTemplate("missing_subject.md")
self.assertIn("subject", str(ctx.exception).lower())
def test_from_email_none_when_omitted(self):
"""When from_email is not specified in frontmatter, getter returns None."""
template = MarkdownTemplate("minimal.md")
self.assertIsNone(template.from_email)
def test_from_email_valid(self):
"""When from_email is valid in frontmatter, getter returns a ValidAddress instance."""
template = MarkdownTemplate("valid_from_email.md")
self.assertIsInstance(template.from_email, ValidAddress)
self.assertEqual(template.from_email.display_name, "Victor Hugo")
self.assertEqual(template.from_email.addr_spec, "hugo@les-miserables.fr")
def test_from_email_invalid_raises_error(self):
"""When from_email is invalid in frontmatter, __init__ raises InvalidAddressError."""
with self.assertRaises(InvalidAddressError):
MarkdownTemplate("invalid_from_email.md")
def test_recipient_addresses_empty_when_omitted_or_empty(self):
"""When to, cc, bcc, reply_to are omitted or empty, getters return empty ValidAddressList instances."""
template = MarkdownTemplate("minimal.md")
self.assertIsInstance(template.to, ValidAddressList)
self.assertEqual(len(template.to), 0)
self.assertIsInstance(template.cc, ValidAddressList)
self.assertEqual(len(template.cc), 0)
self.assertIsInstance(template.bcc, ValidAddressList)
self.assertEqual(len(template.bcc), 0)
self.assertIsInstance(template.reply_to, ValidAddressList)
self.assertEqual(len(template.reply_to), 0)
def test_recipient_addresses_valid(self):
"""When address lists or single addresses are defined, getters return ValidAddressList."""
template = MarkdownTemplate("valid_addresses.md")
self.assertIsInstance(template.to, ValidAddressList)
self.assertEqual(len(template.to), 2)
self.assertEqual(template.to[0].display_name, "Victor Hugo")
self.assertEqual(template.to[1].addr_spec, "antoine@saint-exupery.aero")
self.assertIsInstance(template.cc, ValidAddressList)
self.assertEqual(len(template.cc), 1)
self.assertEqual(template.cc[0].addr_spec, "cesaire@cahiers.net")
self.assertIsInstance(template.bcc, ValidAddressList)
self.assertEqual(len(template.bcc), 1)
self.assertEqual(template.bcc[0].addr_spec, "boris@vian.jazz")
self.assertIsInstance(template.reply_to, ValidAddressList)
self.assertEqual(len(template.reply_to), 1)
self.assertEqual(template.reply_to[0].addr_spec, "contact@example.org")
def test_recipient_addresses_invalid_raises_error(self):
"""When any recipient contains an invalid address, __init__ raises InvalidAddressError."""
with self.assertRaises(InvalidAddressError):
MarkdownTemplate("invalid_recipient.md")
@unittest.skip("Will be implemented in the rendering slice")
def test_subject_rendering(self):
raise NotImplementedError()
def test_addresses(self):
raise NotImplementedError()