work in progress on MarkdownTemplate interface
This commit is contained in:
@ -29,6 +29,8 @@ from .validation import (
|
|||||||
ValidAddressList,
|
ValidAddressList,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
InvalidAddress = InvalidAddressError
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -72,6 +74,44 @@ class MarkdownTemplate:
|
|||||||
self.raw_content = self.load_raw_template(template_name)
|
self.raw_content = self.load_raw_template(template_name)
|
||||||
self.metadata, self.md_body = self.parse_template(self.raw_content)
|
self.metadata, self.md_body = self.parse_template(self.raw_content)
|
||||||
self.validate()
|
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
|
@classmethod
|
||||||
def load_raw_template(cls, template_name: str) -> str:
|
def load_raw_template(cls, template_name: str) -> str:
|
||||||
|
|||||||
@ -6,6 +6,6 @@
|
|||||||
<style>{% block style %}{% endblock %}</style>
|
<style>{% block style %}{% endblock %}</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% block body %}{{ content_html|safe }}{% endblock %}
|
{% block body %}{{ content_html|safe }}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
5
tests/templates/invalid_from_email.md
Normal file
5
tests/templates/invalid_from_email.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
subject: "Test from_email invalide"
|
||||||
|
from_email: "invalid-email-address"
|
||||||
|
---
|
||||||
|
Contenu de test.
|
||||||
7
tests/templates/invalid_recipient.md
Normal file
7
tests/templates/invalid_recipient.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
subject: "Test adresse invalide dans to"
|
||||||
|
to:
|
||||||
|
- "valid@example.org"
|
||||||
|
- "bad address"
|
||||||
|
---
|
||||||
|
Contenu de test.
|
||||||
4
tests/templates/missing_subject.md
Normal file
4
tests/templates/missing_subject.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
to: "test@example.com"
|
||||||
|
---
|
||||||
|
Corps de test sans sujet.
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
subject: "Notification pour {{ missing_user }}"
|
|
||||||
to: "test@example.com"
|
|
||||||
---
|
|
||||||
Corps de test.
|
|
||||||
11
tests/templates/valid_addresses.md
Normal file
11
tests/templates/valid_addresses.md
Normal 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.
|
||||||
5
tests/templates/valid_from_email.md
Normal file
5
tests/templates/valid_from_email.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
subject: "Test from_email valide"
|
||||||
|
from_email: "Victor Hugo <hugo@les-miserables.fr>"
|
||||||
|
---
|
||||||
|
Contenu de test.
|
||||||
104
tests/test_md.py
104
tests/test_md.py
@ -1,16 +1,106 @@
|
|||||||
|
import unittest
|
||||||
|
from django.template.exceptions import TemplateDoesNotExist
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
from django_courriel.md import MarkdownTemplate
|
||||||
|
from django_courriel.validation import (
|
||||||
|
InvalidAddressError,
|
||||||
|
InvalidFrontMatter,
|
||||||
|
ValidAddress,
|
||||||
|
ValidAddressList,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MarkdownTemplateTest(SimpleTestCase):
|
class MarkdownTemplateTest(SimpleTestCase):
|
||||||
|
|
||||||
def test_init(self):
|
def test_init_valid(self):
|
||||||
raise NotImplementedError()
|
"""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):
|
def test_init_template_does_not_exist(self):
|
||||||
raise NotImplementedError()
|
"""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):
|
def test_subject_rendering(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def test_addresses(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user