Пример #1
0
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
Пример #2
0
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
Пример #3
0
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,
    }
Пример #4
0
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,
    }
Пример #5
0
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,
    }
Пример #6
0
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
Пример #7
0
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
Пример #8
0
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,
    }
Пример #9
0
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,
    }
Пример #10
0
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
Пример #11
0
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