seitime-frappe/frappe/email/test_email_body.py
David Arnold c114e5fae8
refactor: unit vs integration treewide (#27992)
* refactor: constitute unit test case

* fix: docs and type hints

* refactor: mark presumed integration test cases explicitly

At time of writing, we now have at least two base test classes:

- frappe.tests.UnitTestCase
- frappe.tests.IntegrationTestCase

They load in their perspective priority queue during execution.

Probably more to come for more efficient queing and scheduling.

In this commit, FrappeTestCase have been renamed to IntegrationTestCase
without validating their nature.

* feat: Move test-related functions from test_runner.py to tests/utils.py

* refactor: add bare UnitTestCase to all doctype tests

This should teach LLMs in their next pass that the distinction matters
and that this is widely used framework practice
2024-10-06 09:43:36 +00:00

211 lines
6.1 KiB
Python

# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import base64
import os
import frappe
from frappe import safe_decode
from frappe.email.doctype.email_queue.email_queue import QueueBuilder, SendMailContext
from frappe.email.email_body import (
get_email,
get_header,
inline_style_in_html,
replace_filename_with_cid,
)
from frappe.email.receive import Email
from frappe.tests import IntegrationTestCase
class TestEmailBody(IntegrationTestCase):
def setUp(self):
email_html = """
<div>
<h3>Hey John Doe!</h3>
<p>This is embedded image you asked for</p>
<img embed="assets/frappe/images/frappe-favicon.svg" />
</div>
"""
email_text = """
Hey John Doe!
This is the text version of this email
"""
img_path = os.path.abspath("assets/frappe/images/frappe-favicon.svg")
with open(img_path, "rb") as f:
img_content = f.read()
img_base64 = base64.b64encode(img_content).decode()
# email body keeps 76 characters on one line
self.img_base64 = fixed_column_width(img_base64, 76)
self.email_string = (
get_email(
recipients=["test@example.com"],
sender="me@example.com",
subject="Test Subject",
content=email_html,
text_content=email_text,
)
.as_string()
.replace("\r\n", "\n")
)
def test_prepare_message_returns_already_encoded_string(self):
uni_chr1 = chr(40960)
uni_chr2 = chr(1972)
QueueBuilder(
recipients=["test@example.com"],
sender="me@example.com",
subject="Test Subject",
message=f"<h1>{uni_chr1}abcd{uni_chr2}</h1>",
text_content="whatever",
).process()
queue_doc = frappe.get_last_doc("Email Queue")
mail_ctx = SendMailContext(queue_doc=queue_doc)
result = mail_ctx.build_message(recipient_email="test@test.com")
self.assertTrue(b"<h1>=EA=80=80abcd=DE=B4</h1>" in result)
def test_prepare_message_returns_cr_lf(self):
QueueBuilder(
recipients=["test@example.com"],
sender="me@example.com",
subject="Test Subject",
message="<h1>\n this is a test of newlines\n" + "</h1>",
text_content="whatever",
).process()
queue_doc = frappe.get_last_doc("Email Queue")
mail_ctx = SendMailContext(queue_doc=queue_doc)
result = safe_decode(mail_ctx.build_message(recipient_email="test@test.com"))
self.assertTrue(result.count("\n") == result.count("\r"))
def test_image(self):
img_signature = """
Content-Type: image/svg+xml
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename="frappe-favicon.svg"
"""
self.assertTrue(img_signature in self.email_string)
self.assertTrue(self.img_base64 in self.email_string)
def test_text_content(self):
text_content = """
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Hey John Doe!
This is the text version of this email
"""
self.assertTrue(text_content in self.email_string)
def test_email_content(self):
html_head = """
Content-Type: text/html; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.=
w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns=3D"http://www.w3.org/1999/xhtml">
"""
html = """<h3>Hey John Doe!</h3>"""
self.assertTrue(html_head in self.email_string)
self.assertTrue(html in self.email_string)
def test_replace_filename_with_cid(self):
original_message = """
<div>
<img embed="assets/frappe/images/frappe-favicon.svg" alt="test" />
<img embed="notexists.jpg" />
</div>
"""
message, inline_images = replace_filename_with_cid(original_message)
processed_message = """
<div>
<img src="cid:{}" alt="test" />
<img />
</div>
""".format(inline_images[0].get("content_id"))
self.assertEqual(message, processed_message)
def test_inline_styling(self):
html = """
<h3>Hi John</h3>
<p>This is a test email</p>
"""
transformed_html = """
<h3>Hi John</h3>
<p style="margin:1em 0 !important">This is a test email</p>
"""
self.assertTrue(transformed_html in inline_style_in_html(html))
def test_email_header(self):
email_html = """
<h3>Hey John Doe!</h3>
<p>This is embedded image you asked for</p>
"""
email_string = get_email(
recipients=["test@example.com"],
sender="me@example.com",
subject="Test Subject\u2028, with line break, \nand Line feed \rand carriage return.",
content=email_html,
header=["Email Title", "green"],
).as_string()
# REDESIGN-TODO: Add style for indicators in email
self.assertTrue("""<span class=3D"indicator indicator-green"></span>""" in email_string)
self.assertTrue("<span>Email Title</span>" in email_string)
self.assertIn(
"Subject: Test Subject, with line break, and Line feed and carriage return.", email_string
)
def test_get_email_header(self):
html = get_header(["This is test", "orange"])
self.assertTrue('<span class="indicator indicator-orange"></span>' in html)
self.assertTrue("<span>This is test</span>" in html)
html = get_header(["This is another test"])
self.assertTrue("<span>This is another test</span>" in html)
html = get_header("This is string")
self.assertTrue("<span>This is string</span>" in html)
def test_8bit_utf_8_decoding(self):
text_content_bytes = b"\xed\x95\x9c\xea\xb8\x80\xe1\xa5\xa1\xe2\x95\xa5\xe0\xba\xaa\xe0\xa4\x8f"
text_content = text_content_bytes.decode("utf-8")
content_bytes = (
b"""MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Disposition: inline
Content-Transfer-Encoding: 8bit
From: test1_@erpnext.com
Reply-To: test2_@erpnext.com
"""
+ text_content_bytes
)
mail = Email(content_bytes)
self.assertEqual(mail.text_content, text_content)
def test_poorly_encoded_messages(self):
mail = Email.decode_email(
"=?iso-2022-jp?B?VEFLQVlBTUEgS2FvcnUgWxskQnxiOzMbKEIgGyRCNzAbKEJd?=\n\t<user@example.com>"
)
self.assertIn("user@example.com", mail)
def test_poorly_encoded_messages2(self):
mail = Email.decode_email(" =?UTF-8?B?X\xe0\xe0Y?= <xy@example.com>")
self.assertIn("xy@example.com", mail)
def fixed_column_width(string, chunk_size):
parts = [string[0 + i : chunk_size + i] for i in range(0, len(string), chunk_size)]
return "\n".join(parts)