Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -38,6 +41,7 @@ services:
- redis
- mysql
- minio
- greenmail

# for running code formatting, isort
devtools:
Expand Down
4 changes: 2 additions & 2 deletions inbox/crispin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down
9 changes: 6 additions & 3 deletions inbox/sendmail/smtp/postel.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ def __init__(
auth_token,
smtp_endpoint,
log,
*,
upgrade_connection=True,
):
self.account_id = account_id
self.email_address = email_address
Expand All @@ -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
Expand All @@ -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):
Expand All @@ -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()
Expand Down
123 changes: 123 additions & 0 deletions tests/imap/test_crispin_client_e2e.py
Original file line number Diff line number Diff line change
@@ -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