def test_retries_temporary_email_failures(_): class FailOnceMail: def __init__(self): self.call_count = 0 def send(self, _): self.call_count += 1 if self.call_count == 1: return SendEmailResult(SendEmailState.TEMPORARY_FAILURE, "oops") return SendEmailResult(SendEmailState.SUCCESS) mail = FailOnceMail() _send_emails( mail, Mock(), logging.create_dev_logger(), [OutgoingEmail("", "", "", 0, "", "")], 0, ) _send_emails( mail, Mock(), logging.create_dev_logger(), [OutgoingEmail("", "", "", 1, "", "")], 0, ) assert mail.call_count == 3
def test_service_keeps_css_classes_if_writing_to_fs(mock_template_store, tmp_path): mail = FsMail("", logging.create_dev_logger(), tmp_path) db = MockDB(is_initialized=True) settings = MockSettings(worker=MockWorker(), mail=mail, db=db) service(settings, Mock()) assert mock_template_store.call_args.kwargs["keep_css_classes"] is True
def test_processes_with_minimal_context_if_full_context_error(): event = { "isSecure": True, "timestamp": 0, "context": { "thisContextIsMissingProperties": True, }, "minimalContext": { "revision": { "revisionId": 1, "link": "link", }, "recipients": [{ "username": "******", "email": "2@mail", "timezoneOffset": 0, "isActor": False, }], }, } mail = MockMail() render = Render(JinjaTemplateStore("", "", False)) logger = logging.create_dev_logger() with spy_on(mail.send) as spy: process_event(event, render, MockThreadStore(), logger, 0, Mock(), mail) assert len(spy.calls) == 1 assert spy.calls[0].args[0].template_path == "minimal"
def test_ses(): client = Mock() mail = SesMail(client, "from@mail", logging.create_dev_logger(), None) mail.send([MOCK_EMAIL]) ses_kwargs = client.send_raw_email.call_args.kwargs assert ses_kwargs["Destinations"] == ["to@mail"] assert ses_kwargs["Source"] == "from@mail" assert "phabricator subject" in ses_kwargs["RawMessage"]["Data"] assert "summary in html" in ses_kwargs["RawMessage"]["Data"] assert "summary in text" in ses_kwargs["RawMessage"]["Data"]
def test_parse_fs_mail(tmp_path): config = _config_parser(""" [email] from_address=from@mail implementation=fs [email-fs] "output_path={tmp_path} """) mail = _parse_mail(config, logging.create_dev_logger()) assert isinstance(mail, FsMail)
def _parse_logger(config: ConfigParser): """Provides a logger set up according to our configuration. The regular logger is noisy and implements the MozLog JSON format, so we also have a "dev" logger that simply prints out each log without any annotations. The correct logger is chosen based on the provided configuration file. """ if config.has_section("dev"): return create_dev_logger() else: return create_logger()
def test_smtp(): smtp_server = Mock() mail = SmtpMail(smtp_server, "from@mail", logging.create_dev_logger(), None) mail.send([MOCK_EMAIL]) smtp_server.sendmail.assert_called_with( "from@mail", "to@mail", mock.ANY, ) mime_message = smtp_server.sendmail.call_args.args[2] assert "phabricator subject" in mime_message assert "summary in html" in mime_message assert "summary in text" in mime_message
def test_poll_caught_up(): position = QueryPosition() position.up_to_key = 10 def pipeline(*unused): return 10 worker = PhabricatorWorker(create_dev_logger(), 60, False) caught_up = worker._poll(MockQueryPositionStore(position=position), MockThreadStore(), pipeline) assert caught_up is True
def test_poll_fresh_events(): position = QueryPosition() position.up_to_key = 10 def pipeline(*unused): return 20 worker = PhabricatorWorker(create_dev_logger(), 60) caught_up = worker._poll( MockQueryPositionStore(position=position), MockThreadStore(), pipeline ) assert caught_up is False assert position.up_to_key == 20
def test_pipeline_updates_position_even_if_no_new_events(): # Sometimes, a feed event may happen that isn't relevant to emails. Phabricator # will report a newer feed position while returning an empty event list. source = MockSource(next_result={ "data": { "events": [], "storyErrors": 0 }, "cursor": { "after": 20 } }) logger = logging.create_dev_logger() pipeline = Pipeline(source, Mock(), MockMail(), logger, 0, Mock(), False) new_position = pipeline.run(MockThreadStore(), 10) assert new_position == 20
def test_integration_pipeline(): source = MockSource( next_result={ "data": { "storyErrors": 0, "events": [ { "isSecure": True, "timestamp": 0, "context": { "eventKind": "revision-reclaimed", "actor": { "userName": "******", "realName": "1" }, "body": { "reviewers": [ { "name": "2", "isActionable": False, "status": "accepted", "recipients": [{ "timezoneOffset": 0, "username": "******", "email": "2@mail", "isActor": False, }], }, { "name": "3", "isActionable": True, "status": "requested-changes", "recipients": [{ "timezoneOffset": 0, "username": "******", "email": "3@mail", "isActor": False, }], }, ], "subscribers": [{ "email": "3@mail", "username": "******", "timezoneOffset": 0, "isActor": False, }], "commentCount": 1, "transactionLink": "link", }, "revision": { "revisionId": 1, "repositoryName": "repo", "link": "link", "bug": { "bugId": 1, "link": "link" }, }, }, }, { "isSecure": False, "timestamp": 1, "context": { "eventKind": "revision-abandoned", "actor": { "userName": "******", "realName": "4" }, "body": { "reviewers": [{ "username": "******", "email": "5@mail", "timezoneOffset": 0, "isActor": False, }], "subscribers": [], "mainCommentMessage": { "asText": "Main comment", "asHtml": "<p>Main comment</p>", }, "inlineComments": [{ "contextKind": "code", "context": { "diff": [{ "lineNumber": 10, "type": "added", "rawContent": "hello world", }] }, "fileContext": "/README:20", "link": "link", "message": { "asText": "great content here.", "asHtml": "<em>great content here.</em>", }, }], "transactionLink": "link", }, "revision": { "revisionId": 2, "name": "name 2", "repositoryName": "repo", "link": "link", }, }, }, ], }, "cursor": { "after": 20 }, }) mail = MockMail() render = Render(JinjaTemplateStore("", "", False)) logger = logging.create_dev_logger() pipeline = Pipeline(source, render, mail, logger, 0, Mock(), False) with spy_on(mail.send) as send_spy, spy_on(source.fetch_next) as fetch_spy: new_position = pipeline.run(MockThreadStore(), 10) assert new_position == 20 assert fetch_spy.calls[0].args[0] == 10 emails = [] for call in send_spy.calls: emails.append(call.args[0]) _assert_mail( emails[0], "D1: (secure bug 1)", "2@mail", "1 reclaimed this revision that you've accepted and submitted a comment.", ) _assert_mail( emails[1], "D1: (secure bug 1)", "3@mail", "1 reclaimed this revision and submitted a comment.", ) _assert_mail( emails[2], "D2: name 2", "5@mail", "4 abandoned this revision and submitted comments.", )
def test_retries_failed_full_sends_with_minimal_emails(send_emails_fn): event = { "timestamp": 0, "isSecure": True, "context": { "eventKind": "revision-reclaimed", "actor": { "userName": "******", "realName": "1" }, "body": { "reviewers": [{ "name": "2", "isActionable": False, "status": "unreviewed", "recipients": [ { "username": "******", "email": "2@mail", "timezoneOffset": 0, "isActor": False, }, { "username": "******", "email": "3@mail", "timezoneOffset": 0, "isActor": False, }, ], }], "subscribers": [], "commentCount": 1, "transactionLink": "link", }, "revision": { "revisionId": 1, "link": "link", "bug": { "bugId": 1, "link": "link" }, }, }, "minimalContext": { "revision": { "revisionId": 1, "link": "link", }, "recipients": [ { "username": "******", "email": "2@mail", "timezoneOffset": 0, "isActor": False, }, { "username": "******", "email": "3@mail", "timezoneOffset": 0, "isActor": False, }, ], }, } send_emails_fn.side_effect = [["2@mail"], []] render = Render(JinjaTemplateStore("", "", False)) logger = logging.create_dev_logger() process_event(event, render, MockThreadStore(), logger, 0, Mock(), None) assert len(send_emails_fn.call_args_list) == 2 assert len(send_emails_fn.call_args_list[1][0][3]) == 1 _assert_mail( send_emails_fn.call_args_list[1][0][3][0], "D1", "2@mail", "An (unknown) action occurred", )
def test_pipeline_returns_same_position_if_fetch_fails(): source = MockSource(fail_on_fetch_next=True) pipeline = Pipeline(source, Mock(), Mock(), logging.create_dev_logger(), 0, Mock(), False) assert pipeline.run(MockThreadStore(), 10) == 10