-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refact: organize the code with adapter
- Loading branch information
Showing
8 changed files
with
207 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
from email.mime.base import MIMEBase | ||
from email.mime.multipart import MIMEMultipart | ||
from email.mime.text import MIMEText | ||
from email.utils import COMMASPACE, formatdate | ||
|
||
|
||
class EmailMIMEAdapter(MIMEMultipart): | ||
__body: str | ||
__attatchments: list[tuple[bytes, str]] | ||
|
||
def __init__( | ||
self, | ||
sender: str, | ||
subject: str, | ||
body: str, | ||
sender_name: str = None, | ||
to: list[str] = [], | ||
cc: list[str] = [], | ||
bcc: list[str] = [], | ||
attatchments: list[tuple[bytes, str]] = [], | ||
) -> None: | ||
super().__init__() | ||
self["Date"] = formatdate(localtime=True) | ||
self.sender = sender | ||
self.subject = subject | ||
self.body = body | ||
self.sender_name = sender_name | ||
self.to = to | ||
self.cc = cc | ||
self.bcc = bcc | ||
self.attatchments = attatchments | ||
|
||
@property | ||
def sender(self): | ||
return self["From"] | ||
|
||
@sender.setter | ||
def sender(self, value: str | tuple[str, str]): | ||
assert isinstance( | ||
value, (str, tuple) | ||
), f"sender expect string or tuple of strings type, but got {type(value)}" | ||
if isinstance(value, str): | ||
self["From"] = value | ||
elif isinstance(value, tuple): | ||
self["From"] = "{0} <{1}>".format(*value) | ||
|
||
@property | ||
def subject(self): | ||
return self["Subject"] | ||
|
||
@subject.setter | ||
def subject(self, value: str): | ||
assert isinstance( | ||
value, str | ||
), f"subject expect string type, but got {type(value)}" | ||
self["Subject"] = value | ||
|
||
@property | ||
def to(self): | ||
return self["To"].split(COMMASPACE) | ||
|
||
@to.setter | ||
def to(self, value: list[str]): | ||
assert isinstance( | ||
value, list | ||
), f"to expect list of strings type, but got {type(value)}" | ||
self["To"] = COMMASPACE.join(value) | ||
|
||
@property | ||
def cc(self): | ||
return self["Cc"].split(COMMASPACE) | ||
|
||
@cc.setter | ||
def cc(self, value: list[str]): | ||
assert isinstance( | ||
value, list | ||
), f"cc expect list of strings type, but got {type(value)}" | ||
self["Cc"] = COMMASPACE.join(value) | ||
|
||
@property | ||
def bcc(self): | ||
return self["Bcc"].split(COMMASPACE) | ||
|
||
@bcc.setter | ||
def bcc(self, value: list[str]): | ||
assert isinstance(value, list), f"bcc expect a list type, but got {type(value)}" | ||
self["Bcc"] = COMMASPACE.join(value) | ||
|
||
@property | ||
def recipients(self): | ||
return self.to + self.cc + self.bcc | ||
|
||
@property | ||
def body(self): | ||
return self.__body | ||
|
||
@body.setter | ||
def body(self, value: str): | ||
assert isinstance(value, str), f"body expect string type, but got {type(value)}" | ||
self.__body = value | ||
|
||
@property | ||
def attatchments(self): | ||
return self.__attatchments | ||
|
||
@attatchments.setter | ||
def attatchments(self, value: list[tuple[bytes, str]]): | ||
assert isinstance( | ||
value, list | ||
), f"attatchments expect a list type, but got {type(value)}" | ||
self.__attatchments = value | ||
|
||
def get_payload(self, *args, **kwargs): | ||
self._payload = [] | ||
self.attach(MIMEText(self.body)) | ||
for attatchment in self.attatchments: | ||
self.__set_attatchment(*attatchment) | ||
return super().get_payload(*args, **kwargs) | ||
|
||
def __set_attatchment(self, data: bytes, filename: str): | ||
attatchment = MIMEBase("application", "octet-stream") | ||
attatchment.set_payload(data) | ||
attatchment.add_header( | ||
"Content-Disposition", f"attachment; filename={filename}" | ||
) | ||
self.attach(attatchment) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
from enum import Enum | ||
|
||
|
||
class ServerSMTP(Enum): | ||
GMAIL = 'smtp.gmail.com' | ||
OUTLOOK = 'smtp-mail.outlook.com' | ||
GMAIL = "smtp.gmail.com" | ||
OUTLOOK = "smtp-mail.outlook.com" | ||
|
||
|
||
class PortSMTP(Enum): | ||
GMAIL = 465 | ||
OUTLOOK = 587 | ||
OUTLOOK = 587 |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,14 @@ | ||
import logging | ||
from email import encoders | ||
from email.mime.base import MIMEBase | ||
from email.mime.multipart import MIMEMultipart | ||
from email.mime.text import MIMEText | ||
from email.utils import COMMASPACE, formatdate | ||
from src.adapters import EmailMIMEAdapter | ||
|
||
from src.clients import BaseEmailClient | ||
from src.models import EmailMime | ||
|
||
|
||
class EmailService: | ||
def __init__(self, client: BaseEmailClient): | ||
self.__client = client | ||
|
||
def _mount_email(self, email: EmailMime) -> MIMEMultipart: | ||
mime = MIMEMultipart() | ||
mime["From"] = f"No Reply <{email.sender}>" | ||
mime["To"] = COMMASPACE.join(email.to) | ||
mime["Cc"] = COMMASPACE.join(email.cc) | ||
mime["Bcc"] = COMMASPACE.join(email.bcc) | ||
mime["Date"] = formatdate(localtime=True) | ||
mime["Subject"] = email.subject | ||
mime.attach(MIMEText(email.body)) | ||
|
||
if email.attatchment: | ||
part = MIMEBase("application", "octet-stream") | ||
part.set_payload(email.attatchment) | ||
encoders.encode_base64(part) | ||
mime.attach(part) | ||
|
||
return mime | ||
|
||
def send_email(self, email: EmailMime) -> None: | ||
def send_email(self, email: EmailMIMEAdapter) -> None: | ||
logging.debug(f"Send e-mail to {email.recipients}") | ||
mime = self._mount_email(email) | ||
with self.__client.smtp() as smtp: | ||
smtp.send_mail(email.recipients, mime.as_string()) | ||
smtp.send_email(email.recipients, email.as_string()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from src.adapters import EmailMIMEAdapter | ||
|
||
SENDER_MOCK = "[email protected]" | ||
SUBJECT_MOCK = "Email Title" | ||
BODY_MOCK = "Email Body" | ||
TO_MOCK = ["[email protected]"] | ||
CC_MOCK = ["[email protected]"] | ||
BCC_MOCK = ["[email protected]"] | ||
ATTATCHMENTS_MOCK = [(b"", "filename.txt")] | ||
|
||
|
||
class TestEmailMIMEAdapter: | ||
def test_initialization(self): | ||
adapter = EmailMIMEAdapter( | ||
sender=SENDER_MOCK, | ||
subject=SUBJECT_MOCK, | ||
body=BODY_MOCK, | ||
to=TO_MOCK, | ||
cc=CC_MOCK, | ||
bcc=BCC_MOCK, | ||
attatchments=ATTATCHMENTS_MOCK, | ||
) | ||
|
||
assert adapter.sender == SENDER_MOCK | ||
assert adapter.subject == SUBJECT_MOCK | ||
assert adapter.body == BODY_MOCK | ||
assert adapter.to == TO_MOCK | ||
assert adapter.cc == CC_MOCK | ||
assert adapter.bcc == BCC_MOCK | ||
assert adapter.attatchments == ATTATCHMENTS_MOCK | ||
assert isinstance(adapter.as_string(), str) | ||
assert adapter.recipients == TO_MOCK + CC_MOCK + BCC_MOCK | ||
|
||
def test_initialization_with_name_sender(self): | ||
sender = ("Example", "[email protected]") | ||
adapter = EmailMIMEAdapter( | ||
sender=sender, | ||
subject=SUBJECT_MOCK, | ||
body=BODY_MOCK, | ||
to=TO_MOCK, | ||
cc=CC_MOCK, | ||
bcc=BCC_MOCK, | ||
attatchments=ATTATCHMENTS_MOCK, | ||
) | ||
|
||
assert sender[0] in adapter.sender | ||
assert sender[1] in adapter.sender |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,8 +6,9 @@ | |
PORT_MOCK = "8888" | ||
ACCOUNT_MOCK = "[email protected]" | ||
PASSWORD_MOCK = "password1234" | ||
TO_MOCK = ['[email protected]'] | ||
MESSAGE_MOCK = 'EMAIL MESSAGE' | ||
TO_MOCK = ["[email protected]"] | ||
MESSAGE_MOCK = "EMAIL MESSAGE" | ||
|
||
|
||
class TestBaseEmailClient: | ||
def setup_method(self): | ||
|
@@ -24,7 +25,7 @@ def test_initialization(self): | |
assert self.client._port == PORT_MOCK | ||
assert self.client._account == ACCOUNT_MOCK | ||
assert self.client._password == PASSWORD_MOCK | ||
assert self.client._client == None | ||
assert self.client._client is None | ||
|
||
@mock.patch("src.clients.BaseEmailClient._client") | ||
@mock.patch("src.clients.BaseEmailClient.__enter__") | ||
|
@@ -48,7 +49,7 @@ def test_initialization(self): | |
assert self.client._port == PortSMTP.GMAIL.value | ||
assert self.client._account == ACCOUNT_MOCK | ||
assert self.client._password == PASSWORD_MOCK | ||
assert self.client._client == None | ||
assert self.client._client is None | ||
|
||
@mock.patch("src.clients.smtplib.SMTP_SSL") | ||
def test_smtp(self, smtp_mock): | ||
|
@@ -62,13 +63,15 @@ def test_smtp(self, smtp_mock): | |
@mock.patch("src.clients.GmailClient._client") | ||
def test_send_mail(self, client_mock): | ||
client_mock.sendmail.return_value = None | ||
result = self.client.send_mail( | ||
result = self.client.send_email( | ||
to_addrs=TO_MOCK, | ||
message=MESSAGE_MOCK, | ||
) | ||
|
||
assert result == None | ||
assert client_mock.sendmail.called_once_with(ACCOUNT_MOCK, TO_MOCK, MESSAGE_MOCK) | ||
assert result is None | ||
assert client_mock.sendmail.called_once_with( | ||
ACCOUNT_MOCK, TO_MOCK, MESSAGE_MOCK | ||
) | ||
|
||
|
||
class TestOutlookClient: | ||
|
@@ -80,7 +83,7 @@ def test_initialization(self): | |
assert self.client._port == PortSMTP.OUTLOOK.value | ||
assert self.client._account == ACCOUNT_MOCK | ||
assert self.client._password == PASSWORD_MOCK | ||
assert self.client._client == None | ||
assert self.client._client is None | ||
|
||
@mock.patch("src.clients.smtplib.SMTP") | ||
def test_smtp(self, smtp_mock): | ||
|
@@ -96,10 +99,12 @@ def test_smtp(self, smtp_mock): | |
@mock.patch("src.clients.OutlookClient._client") | ||
def test_send_mail(self, client_mock): | ||
client_mock.sendmail.return_value = None | ||
result = self.client.send_mail( | ||
result = self.client.send_email( | ||
to_addrs=TO_MOCK, | ||
message=MESSAGE_MOCK, | ||
) | ||
|
||
assert result == None | ||
assert client_mock.sendmail.called_once_with(ACCOUNT_MOCK, TO_MOCK, MESSAGE_MOCK) | ||
assert result is None | ||
assert client_mock.sendmail.called_once_with( | ||
ACCOUNT_MOCK, TO_MOCK, MESSAGE_MOCK | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,15 @@ | ||
from unittest import mock | ||
from src.models import EmailMime | ||
from src.services import EmailService | ||
|
||
|
||
class TestEmailService: | ||
|
||
def setup_method(self): | ||
pass | ||
|
||
|
||
@mock.patch("src.services.EmailService._mount_email") | ||
def test_send_email(self, mount_email): | ||
|
||
def test_send_email(self): | ||
mime_obj = mock.Mock() | ||
mime_obj.as_string.return_value = 'EMAIL MESSAGE' | ||
mount_email.return_value = mime_obj | ||
mime_obj.as_string.return_value = "EMAIL MESSAGE" | ||
mime_obj.recipients = ["[email protected]"] | ||
|
||
client_obj = mock.MagicMock() | ||
client_obj.__enter__.return_value = client_obj | ||
|
@@ -24,18 +19,12 @@ def test_send_email(self, mount_email): | |
|
||
service = EmailService(client_obj) | ||
|
||
email_mime = EmailMime( | ||
sender='[email protected]', | ||
subject='TESTE', | ||
body='BODY', | ||
to=['[email protected]'], | ||
cc=['[email protected]'], | ||
bcc=['[email protected]'], | ||
) | ||
result = service.send_email(email_mime) | ||
result = service.send_email(mime_obj) | ||
|
||
assert client_obj.__enter__.called_once | ||
assert client_obj.__exit__.called_once | ||
assert client_obj.smtp.called_once | ||
assert client_obj.send_mail.called_once_with(email_mime.recipients, mime_obj.as_string.return_value) | ||
assert result == None | ||
assert client_obj.send_mail.called_once_with( | ||
mime_obj.recipients, mime_obj.as_string.return_value | ||
) | ||
assert result is None |