def test_actual_emails(eml: Path, monkeypatch: pytest.MonkeyPatch) -> None: with eml.open("rb") as fp: msg = email.message_from_binary_file(fp, policy=policy.default) monkeypatch.syspath_prepend(EMAIL_DIR) module: Any = import_module(eml.stem) assert email2dict(msg) == module.data assert email2dict(msg, include_all=True) == module.data_all
def test_actual_mboxes(mbox: Path, monkeypatch: pytest.MonkeyPatch) -> None: box = mailbox.mbox(mbox) box.lock() (msg, ) = box box.close() monkeypatch.syspath_prepend(MBOX_DIR) module: Any = import_module(mbox.stem) assert email2dict(msg) == module.data assert email2dict(msg, include_all=True) == module.data_all
def test_compile_multiline_text() -> None: from_addr = Address("Joe Q. Sender", addr_spec="*****@*****.**") to_addrs = ( Address(addr_spec="*****@*****.**"), Address("Jane Q. Recipient", addr_spec="*****@*****.**"), ) draft = DraftMessage( from_addr=from_addr, to_addrs=to_addrs, subject="This is a test e-mail.", ) draft.addtext("This is line 1.\n") draft.addtext("This is line 2.\n") assert email2dict(draft.compile()) == { "unixfrom": None, "headers": { "subject": "This is a test e-mail.", "from": [addr2dict(from_addr)], "to": list(map(addr2dict, to_addrs)), "user-agent": [USER_AGENT], "content-type": { "content_type": "text/plain", "params": {}, }, }, "preamble": None, "content": "This is line 1.\nThis is line 2.\n", "epilogue": None, }
def test_compile_text_no_from() -> None: to_addrs = ( Address(addr_spec="*****@*****.**"), Address("Jane Q. Recipient", addr_spec="*****@*****.**"), ) draft = DraftMessage( from_addr=None, to_addrs=to_addrs, subject="This is a test e-mail.", ) draft.addtext(TEXT) assert email2dict(draft.compile()) == { "unixfrom": None, "headers": { "subject": "This is a test e-mail.", "to": list(map(addr2dict, to_addrs)), "user-agent": [USER_AGENT], "content-type": { "content_type": "text/plain", "params": {}, }, }, "preamble": None, "content": TEXT, "epilogue": None, }
def test_compile_text_quote() -> None: from_addr = Address("Joe Q. Sender", addr_spec="*****@*****.**") to_addrs = ( Address(addr_spec="*****@*****.**"), Address("Jane Q. Recipient", addr_spec="*****@*****.**"), ) draft = DraftMessage( from_addr=from_addr, to_addrs=to_addrs, subject="This is a test e-mail.", ) draft.addtext("This is a quote:\n") draft.addblobquote( b"\xD0is is i\xF1 L\xE1tin\xB9.\n", "iso-8859-1", "latin1.txt", ) assert email2dict(draft.compile()) == { "unixfrom": None, "headers": { "subject": "This is a test e-mail.", "from": [addr2dict(from_addr)], "to": list(map(addr2dict, to_addrs)), "user-agent": [USER_AGENT], "content-type": { "content_type": "text/plain", "params": {}, }, }, "preamble": None, "content": "This is a quote:\n> \xD0is is i\xF1 L\xE1tin\xB9.\n", "epilogue": None, }
def test_simple() -> None: BODY = ("Oh my beloved!\n" "\n" "Wilt thou dine with me on the morrow?\n" "\n" "We're having hot pockets.\n" "\n" "Love, Me\n") msg = EmailMessage() msg["Subject"] = "Meet me" msg["To"] = "*****@*****.**" msg["From"] = "*****@*****.**" msg.set_content(BODY) DICT: MessageDict = { "unixfrom": None, "headers": { "subject": "Meet me", "to": [ { "display_name": "", "address": "*****@*****.**", }, ], "from": [ { "display_name": "", "address": "*****@*****.**", }, ], "content-type": { "content_type": "text/plain", "params": {}, }, }, "preamble": None, "content": BODY, "epilogue": None, } assert email2dict(msg) == DICT DICT_ALL = deepcopy(DICT) DICT_ALL["headers"]["content-type"]["params"]["charset"] = "utf-8" DICT_ALL["headers"]["content-transfer-encoding"] = "7bit" DICT_ALL["headers"]["mime-version"] = "1.0" assert email2dict(msg, include_all=True) == DICT_ALL
def test_daemail( mocker: MockerFixture, opts: List[str], argv: List[str], run_kwargs: Dict[str, Any], cmdresult: Any, mailspec: Dict[str, Any], ) -> None: daemon_mock = mocker.patch("daemon.DaemonContext", autospec=True) run_mock = mocker.patch("subprocess.run", return_value=cmdresult) dtnow_mock = mocker.patch( "daemail.util.dtnow", side_effect=[MOCK_START, MOCK_END], ) runner = CliRunner() with runner.isolated_filesystem(): Path("config.toml").write_text("[outgoing]\n" 'method = "mbox"\n' 'path = "daemail.mbox"\n') r = runner.invoke(main, [*opts, "--config", "config.toml", *argv]) assert r.exit_code == 0, show_result(r) if "--foreground" in opts: assert not daemon_mock.called else: assert daemon_mock.call_count == 1 assert daemon_mock.return_value.__enter__.call_count == 1 run_mock.assert_called_once_with(argv, **run_kwargs) assert dtnow_mock.call_count == 2 assert sorted(os.listdir()) == ["config.toml", "daemail.mbox"] mbox = mailbox.mbox("daemail.mbox") mbox.lock() msgs = list(mbox) mbox.close() assert len(msgs) == 1 msgdict = email2dict(msgs[0]) msgdict["unixfrom"] = None assert msgdict == mailspec
def test_compile_text_undecodable_quote() -> None: from_addr = Address("Joe Q. Sender", addr_spec="*****@*****.**") to_addrs = ( Address(addr_spec="*****@*****.**"), Address("Jane Q. Recipient", addr_spec="*****@*****.**"), ) draft = DraftMessage( from_addr=from_addr, to_addrs=to_addrs, subject="This is a test e-mail.", ) draft.addtext("This is a quote:\n") draft.addblobquote( b"\xD0is is i\xF1 L\xE1tin\xB9.\n", "utf-8", "latin1.txt", ) assert email2dict(draft.compile()) == { "unixfrom": None, "headers": { "subject": "This is a test e-mail.", "from": [addr2dict(from_addr)], "to": list(map(addr2dict, to_addrs)), "user-agent": [USER_AGENT], "content-type": { "content_type": "multipart/mixed", "params": {}, }, }, "preamble": None, "content": [ { "unixfrom": None, "headers": { "content-type": { "content_type": "text/plain", "params": {}, }, }, "preamble": None, "content": "This is a quote:\n", "epilogue": None, }, { "unixfrom": None, "headers": { "content-type": { "content_type": "application/octet-stream", "params": {}, }, "content-disposition": { "disposition": "inline", "params": {"filename": "latin1.txt"}, }, }, "preamble": None, "content": b"\xD0is is i\xF1 L\xE1tin\xB9.\n", "epilogue": None, }, ], "epilogue": None, }
def test_sendmail_failure(mocker: MockerFixture) -> None: daemon_mock = mocker.patch("daemon.DaemonContext", autospec=True) run_mock = mocker.patch( "subprocess.run", side_effect=[ SimpleNamespace( returncode=0, stdout=b"This is the output.\n", stderr=None, ), subprocess.CalledProcessError( returncode=1, cmd=["sendmail", "-i", "-t"], output=b"All the foos are bar when they should be baz.\n", stderr=b"", ), ], ) dtnow_mock = mocker.patch( "daemail.util.dtnow", side_effect=[MOCK_START, MOCK_END], ) runner = CliRunner() argv = ["not-a-real-command", "-x", "foo.txt"] with runner.isolated_filesystem(): Path("config.toml").write_text('[outgoing]\nmethod = "command"\n') r = runner.invoke( main, [ "-t", "*****@*****.**", "-f", "Me <*****@*****.**>", "-c", "config.toml", *argv, ], ) assert r.exit_code == 0, show_result(r) assert daemon_mock.call_count == 1 assert daemon_mock.return_value.__enter__.call_count == 1 assert run_mock.call_args_list == [ mocker.call(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT), mocker.call( ["sendmail", "-i", "-t"], shell=False, input=mocker.ANY, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ), ] sent_msg = email.message_from_bytes( run_mock.call_args_list[1][1]["input"], policy=policy.default, ) assert email2dict(sent_msg) == { "unixfrom": None, "headers": { "from": [{ "display_name": "Me", "address": "*****@*****.**" }], "to": [{ "display_name": "", "address": "*****@*****.**" }], "subject": "[DONE] not-a-real-command -x foo.txt", "user-agent": [USER_AGENT], "content-type": { "content_type": "text/plain", "params": {}, }, }, "preamble": None, "content": ("Start Time: 2020-03-11 16:22:32.010203-04:00\n" "End Time: 2020-03-11 16:24:19.102030-04:00\n" "Exit Status: 0\n" "\n" "Output:\n" "> This is the output.\n"), "epilogue": None, } assert dtnow_mock.call_count == 2 assert sorted(os.listdir()) == ["config.toml", "dead.letter"] mbox = mailbox.mbox("dead.letter") mbox.lock() dead_msgs = list(mbox) mbox.close() assert len(dead_msgs) == 1 msgdict = email2dict(dead_msgs[0]) msgdict["unixfrom"] = None assert msgdict == { "unixfrom": None, "headers": { "from": [{ "display_name": "Me", "address": "*****@*****.**" }], "to": [{ "display_name": "", "address": "*****@*****.**" }], "subject": "[DONE] not-a-real-command -x foo.txt", "user-agent": [USER_AGENT], "content-type": { "content_type": "text/plain", "params": {}, }, }, "preamble": None, "content": ("Start Time: 2020-03-11 16:22:32.010203-04:00\n" "End Time: 2020-03-11 16:24:19.102030-04:00\n" "Exit Status: 0\n" "\n" "Output:\n" "> This is the output.\n" "\n" "Additionally, an error occurred while trying to send this e-mail:\n" "\n" "Command: ['sendmail', '-i', '-t']\n" "Exit Status: 1\n" "\n" "Output:\n" "> All the foos are bar when they should be baz.\n"), "epilogue": None, }
def test_text_html_attachment() -> None: # Adapted from <https://docs.python.org/3/library/email.examples.html> msg = EmailMessage() msg["Subject"] = "Ayons asperges pour le déjeuner" msg["From"] = Address("Pepé Le Pew", "pepe", "example.com") msg["To"] = ( Address("Penelope Pussycat", "penelope", "example.com"), Address("Fabrette Pussycat", "fabrette", "example.com"), ) TEXT = ( "Salut!\n" "\n" "Cela ressemble à un excellent recipie[1] déjeuner.\n" "\n" "[1] http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718\n" "\n" "--Pepé\n") msg.set_content(TEXT) asparagus_cid = make_msgid() HTML = ("<html>\n" " <head></head>\n" " <body>\n" " <p>Salut!</p>\n" " <p>Cela ressemble à un excellent\n" ' <a href="http://www.yummly.com/recipe/Roasted-Asparagus-' 'Epicurious-203718">\n' " recipie\n" " </a> déjeuner.\n" " </p>\n" f' <img src="cid:{asparagus_cid[1:-1]}" />\n' " </body>\n" "</html>\n") msg.add_alternative(HTML, subtype="html") IMG = b"\1\2\3\4\5" msg.get_payload()[1].add_related(IMG, "image", "png", cid=asparagus_cid) DICT: MessageDict = { "unixfrom": None, "headers": { "subject": "Ayons asperges pour le déjeuner", "from": [ { "display_name": "Pepé Le Pew", "address": "*****@*****.**", }, ], "to": [ { "display_name": "Penelope Pussycat", "address": "*****@*****.**", }, { "display_name": "Fabrette Pussycat", "address": "*****@*****.**", }, ], "content-type": { "content_type": "multipart/alternative", "params": {}, }, }, "preamble": None, "content": [ { "unixfrom": None, "headers": { "content-type": { "content_type": "text/plain", "params": {}, }, }, "preamble": None, "content": TEXT, "epilogue": None, }, { "unixfrom": None, "headers": { "content-type": { "content_type": "multipart/related", "params": {}, }, }, "preamble": None, "content": [ { "unixfrom": None, "headers": { "content-type": { "content_type": "text/html", "params": {}, }, }, "preamble": None, "content": HTML, "epilogue": None, }, { "unixfrom": None, "headers": { "content-type": { "content_type": "image/png", "params": {}, }, "content-disposition": { "disposition": "inline", "params": {}, }, "content-id": [asparagus_cid], }, "preamble": None, "content": IMG, "epilogue": None, }, ], "epilogue": None, }, ], "epilogue": None, } assert email2dict(msg) == DICT DICT_ALL = deepcopy(DICT) DICT_ALL["headers"]["mime-version"] = "1.0" DICT_ALL["content"][0]["headers"]["content-transfer-encoding"] = "8bit" DICT_ALL["content"][0]["headers"]["content-type"]["params"][ "charset"] = "utf-8" DICT_ALL["content"][1]["headers"]["mime-version"] = "1.0" DICT_ALL["content"][1]["content"][0]["headers"][ "content-transfer-encoding"] = "quoted-printable" DICT_ALL["content"][1]["content"][0]["headers"]["content-type"]["params"][ "charset"] = "utf-8" DICT_ALL["content"][1]["content"][1]["headers"]["mime-version"] = "1.0" DICT_ALL["content"][1]["content"][1]["headers"][ "content-transfer-encoding"] = "base64" assert email2dict(msg, include_all=True) == DICT_ALL
def test_text_image_mixed() -> None: PNG = bytes.fromhex("89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52" "00 00 00 10 00 00 00 10 08 06 00 00 00 1f f3 ff" "61 00 00 00 06 62 4b 47 44 00 ff 00 ff 00 ff a0" "bd a7 93 00 00 00 09 70 48 59 73 00 00 00 48 00" "00 00 48 00 46 c9 6b 3e 00 00 00 09 76 70 41 67" "00 00 00 10 00 00 00 10 00 5c c6 ad c3 00 00 00" "5b 49 44 41 54 38 cb c5 92 51 0a c0 30 08 43 7d" "b2 fb 5f 39 fb 12 da 61 a9 c3 8e f9 a7 98 98 48" "90 64 9d f2 16 da cc ae b1 01 26 39 92 d8 11 10" "16 9e e0 8c 64 dc 89 b9 67 80 ca e5 f3 3f a8 5c" "cd 76 52 05 e1 b5 42 ea 1d f0 91 1f b4 09 78 13" "e5 52 0e 00 ad 42 f5 bf 85 4f 14 dc 46 b3 32 11" "6c b1 43 99 00 00 00 00 49 45 4e 44 ae 42 60 82") body = EmailMessage() body.set_content("This is part 1.\n") image = EmailMessage() image.set_content( PNG, "image", "png", disposition="inline", filename="ternary.png", ) msg = EmailMessage() msg["Subject"] = "Text and an image" msg.make_mixed() msg.attach(body) msg.attach(image) DICT: MessageDict = { "unixfrom": None, "headers": { "subject": "Text and an image", "content-type": { "content_type": "multipart/mixed", "params": {}, }, }, "preamble": None, "content": [ { "unixfrom": None, "headers": { "content-type": { "content_type": "text/plain", "params": {}, }, }, "preamble": None, "content": "This is part 1.\n", "epilogue": None, }, { "unixfrom": None, "headers": { "content-type": { "content_type": "image/png", "params": {}, }, "content-disposition": { "disposition": "inline", "params": { "filename": "ternary.png", }, }, }, "preamble": None, "content": PNG, "epilogue": None, }, ], "epilogue": None, } assert email2dict(msg) == DICT DICT_ALL = deepcopy(DICT) DICT_ALL["content"][0]["headers"]["mime-version"] = "1.0" DICT_ALL["content"][0]["headers"]["content-transfer-encoding"] = "7bit" DICT_ALL["content"][0]["headers"]["content-type"]["params"][ "charset"] = "utf-8" DICT_ALL["content"][1]["headers"]["mime-version"] = "1.0" DICT_ALL["content"][1]["headers"]["content-transfer-encoding"] = "base64" assert email2dict(msg, include_all=True) == DICT_ALL