send_custom_email: Add dry-run flag to verify recipients and email.

Add a `--dry-run` flag to send_custom_email management command
in order to provide a mechanism to verify the emails of the recipients
and the text of the email being sent before actually sending them.

Add tests to:
- Check that no emails are actually sent when we are in the dry-run mode.
- Check if the emails are printed correctly when we are in the dry-run mode.

Fixes #17767
This commit is contained in:
Siddharth Asthana 2021-04-07 04:57:02 +05:30 committed by Alex Vandiver
parent 9e19490c52
commit a81c4b5e4c
4 changed files with 71 additions and 0 deletions

View File

@ -213,6 +213,7 @@ def send_email(
context: Dict[str, Any] = {}, context: Dict[str, Any] = {},
realm: Optional[Realm] = None, realm: Optional[Realm] = None,
connection: Optional[BaseEmailBackend] = None, connection: Optional[BaseEmailBackend] = None,
dry_run: bool = False,
) -> None: ) -> None:
mail = build_email( mail = build_email(
template_prefix, template_prefix,
@ -228,6 +229,10 @@ def send_email(
template = template_prefix.split("/")[-1] template = template_prefix.split("/")[-1]
logger.info("Sending %s email to %s", template, mail.to) logger.info("Sending %s email to %s", template, mail.to)
if dry_run:
print(mail.message().get_payload()[0])
return
if connection is None: if connection is None:
connection = get_connection() connection = get_connection()
# This will call .open() for us, which is a no-op if it's already open; # This will call .open() for us, which is a no-op if it's already open;
@ -469,4 +474,8 @@ def send_custom_email(users: List[UserProfile], options: Dict[str, Any]) -> None
options.get("from_name"), parsed_email_template.get("from"), "from_name" options.get("from_name"), parsed_email_template.get("from"), "from_name"
), ),
context=context, context=context,
dry_run=options["dry_run"],
) )
if options["dry_run"]:
break

View File

@ -37,6 +37,11 @@ class Command(ZulipBaseCommand):
parser.add_argument( parser.add_argument(
"--admins-only", help="Send only to organization administrators", action="store_true" "--admins-only", help="Send only to organization administrators", action="store_true"
) )
parser.add_argument(
"--dry-run",
action="store_true",
help="Prints emails of the recipients and text of the email.",
)
self.add_user_list_args( self.add_user_list_args(
parser, parser,
@ -60,3 +65,8 @@ class Command(ZulipBaseCommand):
raise error raise error
send_custom_email(users, options) send_custom_email(users, options)
if options["dry_run"]:
print("Would send the above email to:")
for user in users:
print(f" {user.email}")

View File

@ -38,6 +38,7 @@ class TestCustomEmails(ZulipTestCase):
"reply_to": reply_to, "reply_to": reply_to,
"subject": email_subject, "subject": email_subject,
"from_name": from_name, "from_name": from_name,
"dry_run": False,
}, },
) )
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
@ -56,6 +57,7 @@ class TestCustomEmails(ZulipTestCase):
[hamlet], [hamlet],
{ {
"markdown_template_path": markdown_template_path, "markdown_template_path": markdown_template_path,
"dry_run": False,
}, },
) )
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
@ -79,6 +81,7 @@ class TestCustomEmails(ZulipTestCase):
{ {
"markdown_template_path": markdown_template_path, "markdown_template_path": markdown_template_path,
"from_name": from_name, "from_name": from_name,
"dry_run": False,
}, },
) )
@ -89,6 +92,7 @@ class TestCustomEmails(ZulipTestCase):
{ {
"markdown_template_path": markdown_template_path, "markdown_template_path": markdown_template_path,
"subject": email_subject, "subject": email_subject,
"dry_run": False,
}, },
) )
@ -109,6 +113,7 @@ class TestCustomEmails(ZulipTestCase):
{ {
"markdown_template_path": markdown_template_path, "markdown_template_path": markdown_template_path,
"subject": email_subject, "subject": email_subject,
"dry_run": False,
}, },
) )
@ -119,6 +124,7 @@ class TestCustomEmails(ZulipTestCase):
{ {
"markdown_template_path": markdown_template_path, "markdown_template_path": markdown_template_path,
"from_name": from_name, "from_name": from_name,
"dry_run": False,
}, },
) )
@ -136,11 +142,31 @@ class TestCustomEmails(ZulipTestCase):
{ {
"markdown_template_path": markdown_template_path, "markdown_template_path": markdown_template_path,
"admins_only": True, "admins_only": True,
"dry_run": False,
}, },
) )
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
self.assertIn(admin_user.delivery_email, mail.outbox[0].to[0]) self.assertIn(admin_user.delivery_email, mail.outbox[0].to[0])
def test_send_custom_email_dry_run(self) -> None:
hamlet = self.example_user("hamlet")
email_subject = "subject_test"
reply_to = "reply_to_test"
from_name = "from_name_test"
markdown_template_path = "templates/zerver/tests/markdown/test_nested_code_blocks.md"
with patch("builtins.print") as _:
send_custom_email(
[hamlet],
{
"markdown_template_path": markdown_template_path,
"reply_to": reply_to,
"subject": email_subject,
"from_name": from_name,
"dry_run": True,
},
)
self.assertEqual(len(mail.outbox), 0)
class TestFollowupEmails(ZulipTestCase): class TestFollowupEmails(ZulipTestCase):
def test_day1_email_context(self) -> None: def test_day1_email_context(self) -> None:

View File

@ -569,3 +569,29 @@ class TestExport(ZulipTestCase):
call("\033[94mExporting realm\033[0m: zulip"), call("\033[94mExporting realm\033[0m: zulip"),
], ],
) )
class TestSendCustomEmail(ZulipTestCase):
COMMAND_NAME = "send_custom_email"
def test_custom_email_with_dry_run(self) -> None:
path = "templates/zerver/tests/markdown/test_nested_code_blocks.md"
user = self.example_user("hamlet")
with patch("builtins.print") as mock_print:
call_command(
self.COMMAND_NAME,
"-r=zulip",
f"--path={path}",
f"-u={user.delivery_email}",
"--subject=Test email",
"--from-name=zulip@testserver.com",
"--dry-run",
)
self.assertEqual(
mock_print.mock_calls[1:],
[
call("Would send the above email to:"),
call(" user10@zulip.testserver"),
],
)