import pytest from ....src.email_composer import composer, Base64Attachment from ....src.constants import EmailContentType, EmailHeaders, EmailPayload from unittest.mock import Mock import base64 class TestEmailComposer: _class = composer.EmailComposer sample_mandatory_args = { "source": "theorchard@theorchard.com", "to": ["john.doe@theorchard.com"], "subject": "Hello", "text": "Have a nice day!", } @pytest.fixture def instance(self): return self._class(**self.sample_mandatory_args) def test_init(self, instance): """Test instantiation with mandatory arguments.""" assert True # Will pass if instance fixture runs correctly def test_recipients(self): """Test `recipients` property.""" cc_redundant = self.sample_mandatory_args["to"] * 3 cc = ["jane.doe@theorchard.com"] bcc = ["jane.doe@sonymusicpde.com"] * 2 instance = self._class( **self.sample_mandatory_args, cc=cc_redundant + cc, bcc=bcc ) assert sorted(instance.recipients) == sorted( [ *self.sample_mandatory_args["to"], "jane.doe@theorchard.com", "jane.doe@sonymusicpde.com", ] ) def test_compose_no_attachments(self, instance): """Test `compose` method without attachments.""" msg = instance.compose() assert msg.get(EmailPayload.SUBJECT) == self.sample_mandatory_args["subject"] assert msg.get(EmailPayload.FROM) == self.sample_mandatory_args["source"] assert msg.get(EmailPayload.TO) == ",".join(self.sample_mandatory_args["to"]) assert msg.get_content_type() == EmailContentType.MULTIPART @pytest.mark.parametrize( "attr,msg_prop", [("_cc", EmailPayload.CC), ("_bcc", EmailPayload.BCC)] ) def test_compose_with_cc_bcc(self, mocker, instance, attr, msg_prop): """Test `compose` method without attachments.""" emails = ["john.doe@theorchard.com", "jane.doe@theorchard.com"] mocker.patch.object(instance, attr, emails) msg = instance.compose() assert msg.get(msg_prop) == ",".join(emails) def test_compose_with_reply_to(self, mocker, instance): """Test `compose` method with reply-to.""" emails = ["john.doe@theorchard.com", "jane.doe@theorchard.com"] mocker.patch.object(instance, "_reply_to", emails) msg = instance.compose() assert msg.get(EmailHeaders.REPLY_TO) == ",".join(emails) def test_compose_with_attachments(self, mocker, instance): """Test `compose` method with attachments.""" mock_attachments = [Mock()] * 5 mocker.patch.object(instance, "_attachments", mock_attachments) patcher_attach_attachment = mocker.patch.object(instance, "_attach_attachment") instance.compose() assert patcher_attach_attachment.call_count == len( mock_attachments ), f"Should attach {len(mock_attachments)} attachments." def test_attach_body_without_html(self, instance): """Test `attach_body` method with HTML body.""" msg = instance.compose() content = msg.get_payload()[0].as_string() assert not instance._html assert ( EmailContentType.HTML.value not in content ), "Expected the payload to not contain any HTML content types." def test_attach_body_with_html(self, mocker, instance): """Test `attach_body` method with HTML body.""" html_string = "Hello" mocker.patch.object(instance, "_html", html_string) msg = instance.compose() content = msg.get_payload()[0].as_string() assert instance._html assert self.sample_mandatory_args["text"] in content, "Expected plain text." assert html_string in content, "Expected HTML." def test_attach_attachment(self, mocker, instance): """Test `attach_attachment` method.""" test_file_name = "test.txt" payload_as_b64 = base64.b64encode(b"Hello").decode() test_attachment = Base64Attachment( file_name=test_file_name, mime_type="text/plain", attachment=payload_as_b64 ) mocker.patch.object(instance, "_attachments", [test_attachment]) assert len(instance._attachments) == 1, "Expected one attachment." msg = instance.compose() attachment_payload = msg.get_payload()[1].as_string() assert ( f'Content-Disposition: attachment; filename="{test_file_name}"' in attachment_payload ) def test_concatenate(self, instance): """Test `concatenate` method.""" emails = ["john.doe@theorchard.com", "jane.doe@theorchard.com"] result = instance._concatenate(emails) assert result == ",".join(emails)