diff options
18 files changed, 70 insertions, 114 deletions
diff --git a/service/pixelated/adapter/model/mail.py b/service/pixelated/adapter/model/mail.py index 99ff0297..7c4223de 100644 --- a/service/pixelated/adapter/model/mail.py +++ b/service/pixelated/adapter/model/mail.py @@ -14,20 +14,21 @@  # You should have received a copy of the GNU Affero General Public License  # along with Pixelated. If not, see <http://www.gnu.org/licenses/>.  import json +import os +import re +import logging +import dateutil.parser as dateparser  from uuid import uuid4 +from email import message_from_file  from email.mime.text import MIMEText  from email.header import decode_header - +from email.MIMEMultipart import MIMEMultipart +from pycryptopp.hash import sha256  from leap.mail.imap.fields import fields  import leap.mail.walk as walk -import dateutil.parser as dateparser  from pixelated.adapter.model.status import Status -import pixelated.support.date -from email.MIMEMultipart import MIMEMultipart -from pycryptopp.hash import sha256 -import re +from pixelated.support import date  from pixelated.support.functional import compact -import logging  logger = logging.getLogger(__name__) @@ -207,7 +208,7 @@ class InputMail(Mail):          input_mail.headers = {key.capitalize(): value for key, value in mail_dict.get('header', {}).items()}          # XXX this is overriding the property in PixelatedMail -        input_mail.headers['Date'] = pixelated.support.date.iso_now() +        input_mail.headers['Date'] = date.iso_now()          # XXX this is overriding the property in PixelatedMail          input_mail.body = mail_dict.get('body', '') @@ -218,6 +219,20 @@ class InputMail(Mail):          input_mail._status = set(mail_dict.get('status', []))          return input_mail +    @staticmethod +    def from_python_mail(mail): +        input_mail = InputMail() +        input_mail.headers = {key.capitalize(): value for key, value in mail.items()} +        input_mail.headers['Date'] = date.iso_now() +        input_mail.headers['Subject'] = mail['Subject'] +        input_mail.headers['To'] = InputMail.FROM_EMAIL_ADDRESS +        input_mail._mime = MIMEMultipart() +        for payload in mail.get_payload(): +            input_mail._mime.attach(payload) +            if payload.get_content_type() == 'text/plain': +                input_mail.body = payload.as_string() +        return input_mail +  class PixelatedMail(Mail): @@ -305,7 +320,7 @@ class PixelatedMail(Mail):          try:              _headers['Date'] = self._get_date()          except Exception: -            _headers['Date'] = pixelated.support.date.iso_now() +            _headers['Date'] = date.iso_now()          if self.parts and len(self.parts['alternatives']) > 1:              _headers['content_type'] = 'multipart/alternative; boundary="%s"' % self.boundary @@ -341,10 +356,10 @@ class PixelatedMail(Mail):                  else:                      # we can't get a date for this mail, so lets just use now                      logger.warning('Encountered a mail with missing date and received header fields. ID %s' % self.fdoc.content.get('uid', None)) -                    date = pixelated.support.date.iso_now() +                    date = date.iso_now()              return dateparser.parse(date).isoformat()          except (ValueError, TypeError): -            date = pixelated.support.date.iso_now() +            date = date.iso_now()              return dateparser.parse(date).isoformat()      @property @@ -487,3 +502,10 @@ class PixelatedMail(Mail):          dict_mail['replying']['all']['to-field'] = recipients          dict_mail['replying']['all']['cc-field'] = ccs          return dict_mail + + +def welcome_mail(): +    current_path = os.path.dirname(os.path.abspath(__file__)) +    with open(os.path.join(current_path, '..', '..', 'assets', 'welcome.mail')) as mail_template_file: +        mail_template = message_from_file(mail_template_file) +    return InputMail.from_python_mail(mail_template) diff --git a/service/pixelated/adapter/services/draft_service.py b/service/pixelated/adapter/services/draft_service.py index df295eec..c8df0a05 100644 --- a/service/pixelated/adapter/services/draft_service.py +++ b/service/pixelated/adapter/services/draft_service.py @@ -22,13 +22,10 @@ class DraftService(object):          self._mailboxes = mailboxes      def create_draft(self, input_mail): -        pixelated_mail = self._drafts().add(input_mail) +        pixelated_mail = self._mailboxes.drafts.add(input_mail)          return pixelated_mail      def update_draft(self, ident, input_mail):          pixelated_mail = self.create_draft(input_mail) -        self._drafts().remove(ident) +        self._mailboxes.drafts.remove(ident)          return pixelated_mail - -    def _drafts(self): -        return self._mailboxes.drafts() diff --git a/service/pixelated/adapter/services/mail_service.py b/service/pixelated/adapter/services/mail_service.py index e392bc44..4e6b6aa8 100644 --- a/service/pixelated/adapter/services/mail_service.py +++ b/service/pixelated/adapter/services/mail_service.py @@ -76,8 +76,8 @@ class MailService(object):      def move_to_sent(self, last_draft_ident, mail):          if last_draft_ident: -            self.mailboxes.drafts().remove(last_draft_ident) -        return self.mailboxes.sent().add(mail) +            self.mailboxes.drafts.remove(last_draft_ident) +        return self.mailboxes.sent.add(mail)      def mark_as_read(self, mail_id):          mail = self.mail(mail_id) diff --git a/service/pixelated/adapter/services/mailboxes.py b/service/pixelated/adapter/services/mailboxes.py index e9fe6ce5..c2b61ca8 100644 --- a/service/pixelated/adapter/services/mailboxes.py +++ b/service/pixelated/adapter/services/mailboxes.py @@ -15,6 +15,7 @@  # along with Pixelated. If not, see <http://www.gnu.org/licenses/>.  from pixelated.adapter.services.mailbox import Mailbox  from pixelated.adapter.listeners.mailbox_indexer_listener import MailboxIndexerListener +from pixelated.adapter.model.mail import welcome_mail  class Mailboxes(object): @@ -33,15 +34,19 @@ class Mailboxes(object):          MailboxIndexerListener.listen(self.account, mailbox_name, self.querier)          return Mailbox.create(mailbox_name, self.querier, self.search_engine) +    @property      def inbox(self):          return self._create_or_get('INBOX') +    @property      def drafts(self):          return self._create_or_get('DRAFTS') +    @property      def trash(self):          return self._create_or_get('TRASH') +    @property      def sent(self):          return self._create_or_get('SENT') @@ -49,10 +54,10 @@ class Mailboxes(object):          return [self._create_or_get(leap_mailbox_name) for leap_mailbox_name in self.account.mailboxes]      def move_to_trash(self, mail_id): -        return self._move_to(mail_id, self.trash()) +        return self._move_to(mail_id, self.trash)      def move_to_inbox(self, mail_id): -        return self._move_to(mail_id, self.inbox()) +        return self._move_to(mail_id, self.inbox)      def _move_to(self, mail_id, mailbox):          mail = self.querier.mail(mail_id) @@ -62,3 +67,8 @@ class Mailboxes(object):      def mail(self, mail_id):          return self.querier.mail(mail_id) + +    def add_welcome_mail_for_fresh_user(self): +        if self.inbox.fresh: +            mail = welcome_mail() +            self.inbox.add(mail) diff --git a/service/pixelated/application.py b/service/pixelated/application.py index b63e10fb..55946a5e 100644 --- a/service/pixelated/application.py +++ b/service/pixelated/application.py @@ -26,7 +26,7 @@ from OpenSSL import crypto  from pixelated.config import arguments  from pixelated.resources import loading_page -from pixelated.config.initialize_leap import initialize_leap +from pixelated.config.leap import initialize_leap  from pixelated.config import logger, app_factory diff --git a/service/pixelated/config/app_factory.py b/service/pixelated/config/app_factory.py index 477317e1..717c2bb9 100644 --- a/service/pixelated/config/app_factory.py +++ b/service/pixelated/config/app_factory.py @@ -23,7 +23,6 @@ from pixelated.adapter.soledad.soledad_querier import SoledadQuerier  from pixelated.adapter.search import SearchEngine  from pixelated.adapter.services.draft_service import DraftService  from pixelated.adapter.listeners.mailbox_indexer_listener import MailboxIndexerListener -from .welcome_mail import check_welcome_mail  def init_app(leap_home, leap_session): @@ -37,7 +36,8 @@ def init_app(leap_home, leap_session):                                         lambda: leap_session.smtp.ensure_running())      pixelated_mailboxes = Mailboxes(leap_session.account, soledad_querier, search_engine) -    check_welcome_mail(pixelated_mailboxes.inbox()) + +    pixelated_mailboxes.add_welcome_mail_for_fresh_user()      draft_service = DraftService(pixelated_mailboxes)      mail_service = MailService(pixelated_mailboxes, pixelated_mail_sender, soledad_querier, search_engine) diff --git a/service/pixelated/config/initialize_leap.py b/service/pixelated/config/leap.py index 1f8c5655..0ff6ea18 100644 --- a/service/pixelated/config/initialize_leap.py +++ b/service/pixelated/config/leap.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import  from pixelated.config import credentials  from leap.common.events import server as events_server  import pixelated.bitmask_libraries.certs as certs diff --git a/service/pixelated/config/welcome_mail.py b/service/pixelated/config/welcome_mail.py deleted file mode 100644 index 5dcbf9bc..00000000 --- a/service/pixelated/config/welcome_mail.py +++ /dev/null @@ -1,42 +0,0 @@ -# -# Copyright (c) 2014 ThoughtWorks, Inc. -# -# Pixelated is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pixelated is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Pixelated. If not, see <http://www.gnu.org/licenses/>. -import os -from pixelated.adapter.model.mail import InputMail -from pixelated.support.date import iso_now -from email import message_from_file -from email.MIMEMultipart import MIMEMultipart - - -def check_welcome_mail(mailbox): -    if mailbox.fresh: -        welcome_mail = build_welcome_mail() -        mailbox.add(welcome_mail) - - -def build_welcome_mail(): -    current_path = os.path.dirname(os.path.abspath(__file__)) -    with open(os.path.join(current_path, '..', 'assets', 'welcome.mail')) as mail_template_file: -        mail_template = message_from_file(mail_template_file) -    welcome_mail = InputMail() -    welcome_mail.headers['To'] = InputMail.FROM_EMAIL_ADDRESS -    welcome_mail.headers['Subject'] = mail_template['Subject'] -    welcome_mail.headers['Date'] = iso_now() -    welcome_mail._mime = MIMEMultipart() -    for payload in mail_template.get_payload(): -        welcome_mail._mime.attach(payload) -        if payload.get_content_type() == 'text/plain': -            welcome_mail.body = payload.as_string() -    return welcome_mail diff --git a/service/pixelated/maintenance.py b/service/pixelated/maintenance.py index 756ad435..97504103 100644 --- a/service/pixelated/maintenance.py +++ b/service/pixelated/maintenance.py @@ -17,7 +17,7 @@  from mailbox import Maildir  from twisted.internet import reactor, defer  from twisted.internet.threads import deferToThread -from pixelated.config.initialize_leap import initialize_leap +from pixelated.config.leap import initialize_leap  from pixelated.config import logger, arguments  from leap.mail.imap.fields import WithMsgFields diff --git a/service/pixelated/resources/mails_resource.py b/service/pixelated/resources/mails_resource.py index 3822abd3..c4b578ba 100644 --- a/service/pixelated/resources/mails_resource.py +++ b/service/pixelated/resources/mails_resource.py @@ -73,7 +73,7 @@ class MailsResource(Resource):          def on_error(event):              delivery_error_mail = InputMail.delivery_error_template(delivery_address=event.content) -            self._mail_service.mailboxes.inbox().add(delivery_error_mail) +            self._mail_service.mailboxes.inbox.add(delivery_error_mail)          register(signal=proto.SMTP_SEND_MESSAGE_ERROR, callback=on_error) diff --git a/service/test/integration/test_contacts.py b/service/test/integration/test_contacts.py index f9cde9e5..1d82b0d7 100644 --- a/service/test/integration/test_contacts.py +++ b/service/test/integration/test_contacts.py @@ -87,7 +87,7 @@ class ContactsTest(SoledadTestBase):          self.add_mail_to_inbox(to_be_bounced)          bounced_mail_template = MailBuilder().build_input_mail() -        bounced_mail = self.mailboxes.inbox().add(bounced_mail_template) +        bounced_mail = self.mailboxes.inbox.add(bounced_mail_template)          bounced_mail.hdoc.content = self._bounced_mail_hdoc_content()          bounced_mail.save()          self.search_engine.index_mail(bounced_mail) diff --git a/service/test/integration/test_tags.py b/service/test/integration/test_tags.py index 976b6d96..168e035f 100644 --- a/service/test/integration/test_tags.py +++ b/service/test/integration/test_tags.py @@ -86,5 +86,5 @@ class TagsTest(SoledadTestBase):              response = self.post_tags(mail.ident, self._tags_json([tag.name.upper()]))              self.assertEquals("None of the following words can be used as tags: %s" % tag.name, response) -        mail = self.mailboxes.inbox().mail(mail.ident) +        mail = self.mailboxes.inbox.mail(mail.ident)          self.assertNotIn('drafts', mail.tags) diff --git a/service/test/integration/test_welcome_mail.py b/service/test/integration/test_welcome_mail.py index ed37f50e..a5ca555a 100644 --- a/service/test/integration/test_welcome_mail.py +++ b/service/test/integration/test_welcome_mail.py @@ -15,21 +15,20 @@  # along with Pixelated. If not, see <http://www.gnu.org/licenses/>.  from test.support.integration import SoledadTestBase -from pixelated.config.welcome_mail import check_welcome_mail  class TestWelcomeMail(SoledadTestBase): -    def test_that_a_fresh_INBOX_will_receive_a_welcome_mail_only_once(self): -        inbox = self.mailboxes.inbox() -        check_welcome_mail(inbox)  # adds a mail -        check_welcome_mail(inbox)  # should not repeat - +    def test_welcome_mail_is_added_only_once(self): +        self.mailboxes.add_welcome_mail_for_fresh_user() +        self.mailboxes.add_welcome_mail_for_fresh_user()          inbox_mails = self.get_mails_by_tag('inbox')          self.assertEquals(1, len(inbox_mails)) +    def test_empty_mailbox_doesnt_mean_fresh_mailbox(self): +        self.mailboxes.add_welcome_mail_for_fresh_user() +        inbox_mails = self.get_mails_by_tag('inbox')          self.delete_mail(inbox_mails[0].ident) -        check_welcome_mail(inbox)  # it is empty, but not fresh anymore - +        self.mailboxes.add_welcome_mail_for_fresh_user()          inbox_mails = self.get_mails_by_tag('inbox')          self.assertEquals(0, len(inbox_mails)) diff --git a/service/test/support/integration/app_test_client.py b/service/test/support/integration/app_test_client.py index 25171353..a1b0c3b8 100644 --- a/service/test/support/integration/app_test_client.py +++ b/service/test/support/integration/app_test_client.py @@ -125,7 +125,7 @@ class AppTestClient(object):          self.soledad_querier.soledad.create_doc(_dict)      def add_mail_to_inbox(self, input_mail): -        mail = self.mailboxes.inbox().add(input_mail) +        mail = self.mailboxes.inbox.add(input_mail)          if input_mail.tags:              mail.update_tags(input_mail.tags)              self.search_engine.index_mail(mail) diff --git a/service/test/unit/adapter/test_draft_service.py b/service/test/unit/adapter/test_draft_service.py index 0dd6cd2a..79eca5f6 100644 --- a/service/test/unit/adapter/test_draft_service.py +++ b/service/test/unit/adapter/test_draft_service.py @@ -12,7 +12,7 @@ class DraftServiceTest(unittest.TestCase):          self.mailboxes = mock()          self.drafts_mailbox = mock()          self.draft_service = DraftService(self.mailboxes) -        when(self.mailboxes).drafts().thenReturn(self.drafts_mailbox) +        self.mailboxes.drafts = self.drafts_mailbox      def test_add_draft(self):          mail = InputMail() diff --git a/service/test/unit/adapter/test_mail_service.py b/service/test/unit/adapter/test_mail_service.py index f4b89f57..f5e29b0c 100644 --- a/service/test/unit/adapter/test_mail_service.py +++ b/service/test/unit/adapter/test_mail_service.py @@ -27,9 +27,9 @@ class TestMailService(unittest.TestCase):          self.drafts = mock()          self.querier = mock()          self.mailboxes = mock() -        self.mailboxes.drafts = lambda: self.drafts -        self.mailboxes.trash = lambda: mock() -        self.mailboxes.sent = lambda: mock() +        self.mailboxes.drafts = self.drafts +        self.mailboxes.trash = mock() +        self.mailboxes.sent = mock()          self.mail_sender = mock()          self.search_engine = mock() diff --git a/service/test/unit/adapter/test_mailboxes.py b/service/test/unit/adapter/test_mailboxes.py index 5b4548eb..6ff3849b 100644 --- a/service/test/unit/adapter/test_mailboxes.py +++ b/service/test/unit/adapter/test_mailboxes.py @@ -23,6 +23,7 @@ from mock import MagicMock  class PixelatedMailboxesTest(unittest.TestCase): +      def setUp(self):          self.querier = mock()          self.search_engine = mock() diff --git a/service/test/unit/config/test_welcome_mail.py b/service/test/unit/config/test_welcome_mail.py deleted file mode 100644 index 3971c73f..00000000 --- a/service/test/unit/config/test_welcome_mail.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (c) 2014 ThoughtWorks, Inc. -# -# Pixelated is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pixelated is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Pixelated. If not, see <http://www.gnu.org/licenses/>. -import unittest - -from pixelated.config.welcome_mail import build_welcome_mail -from pixelated.adapter.model.mail import InputMail - - -class WelcomeMailTest(unittest.TestCase): - -    def test_build_plain_welcome_mail(self): -        user_address = InputMail.FROM_EMAIL_ADDRESS = 'welcomed@user' -        mail = build_welcome_mail() -        self.assertEquals(user_address, mail.to) -        self.assertEquals('Welcome to Pixelated Mail', mail.headers['Subject']) -        self.assertIn('How to use it', mail.body) -        self.assertIn('text/plain', mail._mime.as_string()) -        self.assertIn('text/html', mail._mime.as_string()) -        self.assertTrue(mail.headers['Date'])  | 
