From 88094ff758ecdf8134597347e05845395cb2a6b2 Mon Sep 17 00:00:00 2001 From: Squeaky Date: Tue, 25 Apr 2023 18:05:42 +0200 Subject: [PATCH] test_crispin_client_e2e.py --- docker-compose.yml | 4 + inbox/crispin.py | 4 +- inbox/sendmail/smtp/postel.py | 9 +- tests/imap/test_crispin_client_e2e.py | 123 ++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 tests/imap/test_crispin_client_e2e.py diff --git a/docker-compose.yml b/docker-compose.yml index a5afdafe1..2e100dd7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,6 +25,9 @@ services: - MINIO_BUCKET_NAME=sync-engine - MINIO_DOMAIN=minio + greenmail: + image: greenmail/standalone:1.6.14 + app: image: sync-engine_app build: @@ -38,6 +41,7 @@ services: - redis - mysql - minio + - greenmail # for running code formatting, isort devtools: diff --git a/inbox/crispin.py b/inbox/crispin.py index 1d96bcf17..fa505c08c 100644 --- a/inbox/crispin.py +++ b/inbox/crispin.py @@ -993,7 +993,7 @@ def fetch_headers(self, uids: List[int]) -> Dict[int, Dict[bytes, Any]]: headers.update(self.conn.fetch(uid_chunk, ["BODY.PEEK[HEADER]"])) return headers - def find_by_header(self, header_name, header_value): + def find_by_header(self, header_name: str, header_value: str) -> List[int]: """Find all uids in the selected folder with the given header value.""" all_uids = self.all_uids() # It would be nice to just search by header too, but some backends @@ -1007,7 +1007,7 @@ def find_by_header(self, header_name, header_value): for uid, response in matching_draft_headers.items(): headers = response[b"BODY[HEADER]"] parser = HeaderParser() - header = parser.parsestr(headers).get(header_name) + header = parser.parsestr(headers.decode()).get(header_name) if header == header_value: results.append(uid) diff --git a/inbox/sendmail/smtp/postel.py b/inbox/sendmail/smtp/postel.py index c6f5b6e25..ed7be09a9 100644 --- a/inbox/sendmail/smtp/postel.py +++ b/inbox/sendmail/smtp/postel.py @@ -122,6 +122,8 @@ def __init__( auth_token, smtp_endpoint, log, + *, + upgrade_connection=True, ): self.account_id = account_id self.email_address = email_address @@ -135,7 +137,7 @@ def __init__( "oauth2": self.smtp_oauth2, "password": self.smtp_password, } - self.setup() + self.setup(upgrade_connection=upgrade_connection) def __enter__(self): return self @@ -156,7 +158,7 @@ def _connect(self, host, port): msg = _transform_ssl_error(e.strerror) raise SendMailException(msg, 503) - def setup(self): + def setup(self, *, upgrade_connection=True): host, port = self.smtp_endpoint self.connection: smtplib.SMTP if port in (SMTP_OVER_SSL_PORT, SMTP_OVER_SSL_TEST_PORT): @@ -166,7 +168,8 @@ def setup(self): self.connection = SMTP(timeout=SMTP_TIMEOUT) self._connect(host, port) self.connection._host = host # type: ignore - self._upgrade_connection() + if upgrade_connection: + self._upgrade_connection() # Auth the connection self.connection.ehlo() diff --git a/tests/imap/test_crispin_client_e2e.py b/tests/imap/test_crispin_client_e2e.py new file mode 100644 index 000000000..c81f526b8 --- /dev/null +++ b/tests/imap/test_crispin_client_e2e.py @@ -0,0 +1,123 @@ +import uuid +from email.parser import HeaderParser +from unittest import mock + +import imapclient +import pytest +from flanker import mime + +from inbox.crispin import CrispinClient, Flags, RawFolder, RawMessage +from inbox.sendmail.smtp.postel import SMTPConnection + + +@pytest.fixture +def recipient_email_address(): + return f"{uuid.uuid4().hex}-recipient@example.com" + + +@pytest.fixture +def crispin_client(recipient_email_address): + connection = imapclient.IMAPClient(host="greenmail", ssl=False, port=3143) + connection.login(recipient_email_address, "password") + + client = CrispinClient(-1, {}, recipient_email_address, connection) + client.select_folder("INBOX", mock.Mock()) + return client + + +def test_capabilities(crispin_client): + assert not crispin_client.condstore_supported() + assert crispin_client.idle_supported() + + +def test_folders(crispin_client): + assert crispin_client.folder_separator == "." + assert crispin_client.folder_prefix == "" + + folders = crispin_client.folders() + assert set(folders) == { + RawFolder("INBOX", "inbox"), + } + + folder_names = crispin_client.folder_names() + assert folder_names == { + "inbox": ["INBOX"], + } + + sync_folders = crispin_client.sync_folders() + assert sync_folders == ["INBOX"] + + +@pytest.fixture +def sender_email_address(): + return f"{uuid.uuid4().hex}-sender@example.com" + + +@pytest.fixture +def smtp_client(sender_email_address): + return SMTPConnection( + -1, + sender_email_address, + sender_email_address, + "password", + "password", + ("greenmail", 3025), + mock.Mock(), + upgrade_connection=False, + ) + + +@pytest.fixture +def message(recipient_email_address, smtp_client): + content = "Hello" + + smtp_client.sendmail([recipient_email_address], content) + + return content + + +def test_uids(message, sender_email_address, crispin_client): + (uid,) = crispin_client.all_uids() + (raw_message,) = crispin_client.uids([uid]) + + assert raw_message == RawMessage( + uid=uid, + internaldate=mock.ANY, + flags=(b"\\Recent",), + body=mock.ANY, + g_msgid=None, + g_thrid=None, + g_labels=None, + ) + mimepart = mime.from_string(raw_message.body) + assert dict(mimepart.headers) == { + "Return-Path": f"<{sender_email_address}>", + "Received": mock.ANY, + } + assert mimepart.body.strip() == message + + +def test_flags(message, crispin_client): + (uid,) = crispin_client.all_uids() + flags = crispin_client.flags([uid]) + assert flags == {uid: Flags(flags=(b"\\Recent",), modseq=None)} + + +def test_fetch_headers(message, sender_email_address, crispin_client): + (uid,) = crispin_client.all_uids() + imap_headers = crispin_client.fetch_headers([uid]) + + assert imap_headers == {uid: {b"SEQ": mock.ANY, b"BODY[HEADER]": mock.ANY}} + parser = HeaderParser() + headers = dict(parser.parsestr(imap_headers[uid][b"BODY[HEADER]"].decode())) + + assert headers == {"Return-Path": f"<{sender_email_address}>", "Received": mock.ANY} + + +def test_find_by_header(message, sender_email_address, crispin_client): + return_path_uids = crispin_client.find_by_header( + "Return-Path", f"<{sender_email_address}>" + ) + all_uids = crispin_client.all_uids() + + assert return_path_uids == all_uids