summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--install-pixelated.sh10
-rwxr-xr-xservice/go8
-rw-r--r--service/pixelated/adapter/search.py2
-rw-r--r--service/pixelated/adapter/soledad_querier.py1
-rw-r--r--service/pixelated/controllers/__init__.py7
-rw-r--r--service/pixelated/controllers/tags_controller.py13
-rw-r--r--service/test/functional/features/environment.py22
-rw-r--r--service/test/functional/features/steps/data_setup.py4
-rw-r--r--service/test/integration/delete_mail_test.py13
-rw-r--r--service/test/integration/drafts_test.py9
-rw-r--r--service/test/integration/mark_as_read_unread_test.py25
-rw-r--r--service/test/integration/retrieve_attachment_test.py11
-rw-r--r--service/test/integration/search_test.py100
-rw-r--r--service/test/integration/soledad_querier_test.py16
-rw-r--r--service/test/integration/tags_test.py15
-rw-r--r--service/test/support/integration/__init__.py18
-rw-r--r--service/test/support/integration/app_test_client.py156
-rw-r--r--service/test/support/integration/model.py88
-rw-r--r--service/test/support/integration/soledad_test_base.py83
-rw-r--r--service/test/support/integration_helper.py278
-rw-r--r--service/test/unit/bitmask_libraries/smtp_test.py1
-rw-r--r--web-ui/app/js/mail_list/ui/mail_items/mail_item.js1
-rw-r--r--web-ui/app/js/mail_view/ui/recipients/recipients_input.js3
-rw-r--r--web-ui/app/scss/styles.scss5
-rw-r--r--web-ui/app/templates/mails/full_view.hbs2
-rw-r--r--web-ui/app/templates/mails/single.hbs9
-rw-r--r--web-ui/test/spec/mail_list/ui/mail_list.spec.js2
-rw-r--r--web-ui/test/spec/mail_view/ui/recipients/recipients_input.spec.js26
29 files changed, 525 insertions, 407 deletions
diff --git a/README.md b/README.md
index a23da74b..6c2036c7 100644
--- a/README.md
+++ b/README.md
@@ -23,10 +23,10 @@ On Debian/Ubuntu systems you need:
sudo apt-get install nodejs npm ruby bundler virtualenv git
-Once you have that, run:
+Once you have that, clone the repo and run inside:
```
-curl https://raw.githubusercontent.com/pixelated-project/pixelated-user-agent/master/install-pixelated.sh | /bin/bash
+/bin/bash install-pixelated.sh
```
_____________
diff --git a/install-pixelated.sh b/install-pixelated.sh
index 3aa816d5..3ee52a84 100644
--- a/install-pixelated.sh
+++ b/install-pixelated.sh
@@ -26,16 +26,13 @@ function check_installed() {
}
for dependency in node npm ruby bundle virtualenv git gpg; do
- check_installed $dependency
+ check_installed $dependency
done
-# clone repo
-git clone https://github.com/pixelated-project/pixelated-user-agent
-
# install web-ui dependencies
-cd pixelated-user-agent/web-ui
+cd web-ui
npm install
-node_modules/bower/bin/bower install
+node_modules/bower/bin/bower install --config.interactive=false
bundle install
LC_ALL=en_US.UTF-8 ./go build
@@ -44,6 +41,7 @@ cd ../service
virtualenv .virtualenv
source .virtualenv/bin/activate
./go develop --always-unzip
+pip uninstall -y gnupg; pip install gnupg
# print usage
cat <<EOF
diff --git a/service/go b/service/go
index 9b967b83..5ae8d26b 100755
--- a/service/go
+++ b/service/go
@@ -1,3 +1,9 @@
#!/bin/bash
-python setup.py $*
+if [ "$1" == 'test' ]
+then
+ nosetests --nocapture test/unit
+ nosetests --nocapture test/integration
+else
+ python setup.py $*
+fi
diff --git a/service/pixelated/adapter/search.py b/service/pixelated/adapter/search.py
index cd900f87..6c62ccc3 100644
--- a/service/pixelated/adapter/search.py
+++ b/service/pixelated/adapter/search.py
@@ -102,7 +102,7 @@ class SearchEngine(object):
raw=TEXT(stored=False))
def _create_index(self):
- masterkey = self.soledad_querier.get_index_masterkey
+ masterkey = self.soledad_querier.get_index_masterkey()
storage = EncryptedFileStorage(self.INDEX_FOLDER, masterkey)
return FileIndex.create(storage, self._mail_schema(), indexname='mails')
diff --git a/service/pixelated/adapter/soledad_querier.py b/service/pixelated/adapter/soledad_querier.py
index 2e67a5ab..a11ff7a3 100644
--- a/service/pixelated/adapter/soledad_querier.py
+++ b/service/pixelated/adapter/soledad_querier.py
@@ -26,7 +26,6 @@ class SoledadQuerier:
def __init__(self, soledad):
self.soledad = soledad
- @property
def get_index_masterkey(self):
index_key = self.soledad.get_from_index('by-type', 'index_key')
if len(index_key) == 0:
diff --git a/service/pixelated/controllers/__init__.py b/service/pixelated/controllers/__init__.py
index 9e447d4d..e1c13515 100644
--- a/service/pixelated/controllers/__init__.py
+++ b/service/pixelated/controllers/__init__.py
@@ -22,6 +22,13 @@ def respond_json(entity, request, status_code=200):
return json_response
+def respond_json_deferred(entity, request, status_code=200):
+ json_response = json.dumps(entity)
+ request.responseHeaders.addRawHeader(b"content-type", b"application/json")
+ request.code = status_code
+ request.write(json_response)
+ request.finish()
+
import json
from home_controller import HomeController
diff --git a/service/pixelated/controllers/tags_controller.py b/service/pixelated/controllers/tags_controller.py
index 0b9a94ac..b6741dcc 100644
--- a/service/pixelated/controllers/tags_controller.py
+++ b/service/pixelated/controllers/tags_controller.py
@@ -14,8 +14,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
-from flask import request
-from pixelated.controllers import respond_json
+from pixelated.controllers import respond_json_deferred
+from twisted.internet.threads import deferToThread
class TagsController:
@@ -25,6 +25,9 @@ class TagsController:
def tags(self, request):
query = request.args.get('q', [''])[0]
- skip_default_tags = request.args.get('skipDefaultTags')
- tags = self._search_engine.tags(query=query, skip_default_tags=skip_default_tags)
- return respond_json(tags, request)
+ skip_default_tags = request.args.get('skipDefaultTags', [False])[0]
+
+ d = deferToThread(lambda: self._search_engine.tags(query=query, skip_default_tags=skip_default_tags))
+ d.addCallback(lambda tags: respond_json_deferred(tags, request))
+
+ return d
diff --git a/service/test/functional/features/environment.py b/service/test/functional/features/environment.py
index cb9e0876..537cd969 100644
--- a/service/test/functional/features/environment.py
+++ b/service/test/functional/features/environment.py
@@ -15,27 +15,20 @@
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import time
import multiprocessing
-
-from selenium import webdriver
-from test.support.integration_helper import SoledadTestBase
-import pixelated.runserver
import logging
-import pixelated.controllers.features_controller
+from test.support.integration import AppTestClient
+from selenium import webdriver
+import pixelated
def before_all(context):
- context.soledad_test_base = SoledadTestBase()
- context.soledad_test_base.setup_soledad()
-
- context.mailboxes = context.soledad_test_base.mailboxes
- context.app = pixelated.runserver.app
- context.app.mail_service = context.soledad_test_base.mail_service
pixelated.controllers.features_controller.FeaturesController.DISABLED_FEATURES.append('autoRefresh')
+ client = AppTestClient()
+ context.client = client
logging.disable('INFO')
- worker = lambda app, port: pixelated.runserver.app.run(host='localhost', port=4567,
- logFile=open('/tmp/behave-tests.log', 'w'))
- context._process = multiprocessing.Process(target=worker, args=(context.app, 4567))
+ worker = lambda: client.app.run(host='localhost', port=4567, logFile=open('/tmp/behave-tests.log', 'w'))
+ context._process = multiprocessing.Process(target=worker)
context._process.start()
# we must wait the server start listening
@@ -43,7 +36,6 @@ def before_all(context):
def after_all(context):
- context.soledad_test_base.teardown_soledad()
context._process.terminate()
diff --git a/service/test/functional/features/steps/data_setup.py b/service/test/functional/features/steps/data_setup.py
index 51c5f9b8..4e349f05 100644
--- a/service/test/functional/features/steps/data_setup.py
+++ b/service/test/functional/features/steps/data_setup.py
@@ -13,10 +13,10 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
-from test.support.integration_helper import MailBuilder
+from test.support.integration import MailBuilder
@given('I have a mail in my inbox')
def add_mail_impl(context):
input_mail = MailBuilder().build_input_mail()
- context.soledad_test_base.add_mail_to_inbox(input_mail)
+ context.client.add_mail_to_inbox(input_mail)
diff --git a/service/test/integration/delete_mail_test.py b/service/test/integration/delete_mail_test.py
index 10c09e7a..15232b0d 100644
--- a/service/test/integration/delete_mail_test.py
+++ b/service/test/integration/delete_mail_test.py
@@ -13,22 +13,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 unittest
-from test.support.integration_helper import MailBuilder, SoledadTestBase
+from test.support.integration import *
-class DeleteMailTest(unittest.TestCase, SoledadTestBase):
+class DeleteMailTest(SoledadTestBase):
def setUp(self):
- self.setup_soledad()
+ SoledadTestBase.setUp(self)
def tearDown(self):
- self.teardown_soledad()
+ SoledadTestBase.tearDown(self)
def test_move_mail_to_trash_when_deleting(self):
input_mail = MailBuilder().with_subject('Mail with tags').build_input_mail()
- self.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail)
inbox_mails = self.get_mails_by_tag('inbox')
self.assertEquals(1, len(inbox_mails))
@@ -41,7 +40,7 @@ class DeleteMailTest(unittest.TestCase, SoledadTestBase):
self.assertEquals(1, len(trash_mails))
def test_delete_mail_when_trashing_mail_from_trash_mailbox(self):
- mails = self.add_multiple_to_mailbox(1, 'trash')
+ mails = self.client.add_multiple_to_mailbox(1, 'trash')
self.delete_mail(mails[0].ident)
trash_mails = self.get_mails_by_tag('trash')
diff --git a/service/test/integration/drafts_test.py b/service/test/integration/drafts_test.py
index 5d2118df..2ba14dfd 100644
--- a/service/test/integration/drafts_test.py
+++ b/service/test/integration/drafts_test.py
@@ -13,18 +13,17 @@
#
# 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 test.support.integration_helper import MailBuilder, SoledadTestBase
+from test.support.integration import *
-class DraftsTest(unittest.TestCase, SoledadTestBase):
+class DraftsTest(SoledadTestBase):
def setUp(self):
- self.setup_soledad()
+ SoledadTestBase.setUp(self)
def tearDown(self):
- self.teardown_soledad()
+ SoledadTestBase.tearDown(self)
def test_post_sends_mail_and_deletes_previous_draft_if_it_exists(self):
# creates one draft
diff --git a/service/test/integration/mark_as_read_unread_test.py b/service/test/integration/mark_as_read_unread_test.py
index dc21c7b7..55467e9e 100644
--- a/service/test/integration/mark_as_read_unread_test.py
+++ b/service/test/integration/mark_as_read_unread_test.py
@@ -13,23 +13,22 @@
#
# 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 test.support.integration_helper import MailBuilder, SoledadTestBase
+from test.support.integration import *
from pixelated.adapter.status import Status
-class MarkAsReadUnreadTest(unittest.TestCase, SoledadTestBase):
+class MarkAsReadUnreadTest(SoledadTestBase):
def setUp(self):
- self.setup_soledad()
+ SoledadTestBase.setUp(self)
def tearDown(self):
- self.teardown_soledad()
+ SoledadTestBase.tearDown(self)
def test_mark_single_as_read(self):
input_mail = MailBuilder().build_input_mail()
- self.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail)
mails = self.get_mails_by_tag('inbox')
self.assertNotIn('read', mails[0].status)
@@ -41,7 +40,7 @@ class MarkAsReadUnreadTest(unittest.TestCase, SoledadTestBase):
def test_mark_single_as_unread(self):
input_mail = MailBuilder().with_status([Status.SEEN]).build_input_mail()
- self.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail)
self.mark_as_unread(input_mail.ident)
mail = self.get_mails_by_tag('inbox')[0]
@@ -52,8 +51,8 @@ class MarkAsReadUnreadTest(unittest.TestCase, SoledadTestBase):
input_mail = MailBuilder().with_status([Status.SEEN]).build_input_mail()
input_mail2 = MailBuilder().with_status([Status.SEEN]).build_input_mail()
- self.add_mail_to_inbox(input_mail)
- self.add_mail_to_inbox(input_mail2)
+ self.client.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail2)
self.mark_many_as_unread([input_mail.ident, input_mail2.ident])
@@ -66,8 +65,8 @@ class MarkAsReadUnreadTest(unittest.TestCase, SoledadTestBase):
input_mail = MailBuilder().build_input_mail()
input_mail2 = MailBuilder().build_input_mail()
- self.add_mail_to_inbox(input_mail)
- self.add_mail_to_inbox(input_mail2)
+ self.client.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail2)
mails = self.get_mails_by_tag('inbox')
@@ -86,8 +85,8 @@ class MarkAsReadUnreadTest(unittest.TestCase, SoledadTestBase):
input_mail = MailBuilder().build_input_mail()
input_mail2 = MailBuilder().with_status([Status.SEEN]).build_input_mail()
- self.add_mail_to_inbox(input_mail)
- self.add_mail_to_inbox(input_mail2)
+ self.client.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail2)
mails = self.get_mails_by_tag('inbox')
diff --git a/service/test/integration/retrieve_attachment_test.py b/service/test/integration/retrieve_attachment_test.py
index 37bc3ca2..5f754a27 100644
--- a/service/test/integration/retrieve_attachment_test.py
+++ b/service/test/integration/retrieve_attachment_test.py
@@ -13,18 +13,17 @@
#
# 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 test.support.integration_helper import SoledadTestBase
+from test.support.integration.soledad_test_base import SoledadTestBase
-class RetrieveAttachmentTest(unittest.TestCase, SoledadTestBase):
+class RetrieveAttachmentTest(SoledadTestBase):
def setUp(self):
- self.setup_soledad()
+ SoledadTestBase.setUp(self)
def tearDown(self):
- self.teardown_soledad()
+ SoledadTestBase.tearDown(self)
def test_attachment_content_is_retrieved(self):
ident = 'F4E99C1CEC4D300A4223A96CCABBE0304BDBC31C550A5A03E207A5E4C3C71A22'
@@ -35,7 +34,7 @@ class RetrieveAttachmentTest(unittest.TestCase, SoledadTestBase):
'phash': ident,
'content-type': 'text/plain; charset=US-ASCII; name="attachment_pequeno.txt"'}
- self.add_document_to_soledad(attachment_dict)
+ self.client.add_document_to_soledad(attachment_dict)
attachment = self.get_attachment(ident, 'base64')
diff --git a/service/test/integration/search_test.py b/service/test/integration/search_test.py
index 649f7b96..21326ec7 100644
--- a/service/test/integration/search_test.py
+++ b/service/test/integration/search_test.py
@@ -13,72 +13,86 @@
#
# 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 test.support.integration_helper import MailBuilder, SoledadTestBase
+from nose.twistedtools import deferred
+from test.support.integration import *
-class SearchTest(unittest.TestCase, SoledadTestBase):
+class SearchTest(SoledadTestBase):
def setUp(self):
- self.setup_soledad()
-
- def tearDown(self):
- self.teardown_soledad()
+ SoledadTestBase.setUp(self)
+ @deferred(timeout=SoledadTestBase.DEFERRED_TIMEOUT)
def test_that_tags_returns_all_tags(self):
input_mail = MailBuilder().with_tags(['important']).build_input_mail()
- self.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail)
- all_tags = self.get_tags()
+ d = self.get_tags()
- all_tag_names = [t['name'] for t in all_tags]
- self.assertTrue('inbox' in all_tag_names)
- self.assertTrue('sent' in all_tag_names)
- self.assertTrue('trash' in all_tag_names)
- self.assertTrue('drafts' in all_tag_names)
- self.assertTrue('important' in all_tag_names)
+ def _assert(all_tags):
+ all_tag_names = [t['name'] for t in all_tags]
+ self.assertTrue('inbox' in all_tag_names)
+ self.assertTrue('sent' in all_tag_names)
+ self.assertTrue('trash' in all_tag_names)
+ self.assertTrue('drafts' in all_tag_names)
+ self.assertTrue('important' in all_tag_names)
+ d.addCallback(_assert)
+ return d
+ @deferred(timeout=SoledadTestBase.DEFERRED_TIMEOUT)
def test_that_tags_are_filtered_by_query(self):
input_mail = MailBuilder().with_tags(['ateu', 'catoa', 'luat', 'zuado']).build_input_mail()
- self.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail)
+
+ d = self.get_tags(q=["at"], skipDefaultTags=["true"])
- all_tags = self.get_tags(q=["at"], skipDefaultTags=["true"])
+ def _assert(all_tags):
+ all_tag_names = [t['name'] for t in all_tags]
+ self.assertEqual(3, len(all_tag_names))
+ self.assertTrue('ateu' in all_tag_names)
+ self.assertTrue('catoa' in all_tag_names)
+ self.assertTrue('luat' in all_tag_names)
- all_tag_names = [t['name'] for t in all_tags]
- self.assertEqual(3, len(all_tag_names))
- self.assertTrue('ateu' in all_tag_names)
- self.assertTrue('catoa' in all_tag_names)
- self.assertTrue('luat' in all_tag_names)
+ d.addCallback(_assert)
+ return d
+ @deferred(timeout=SoledadTestBase.DEFERRED_TIMEOUT)
def test_that_default_tags_are_ignorable(self):
input_mail = MailBuilder().with_tags(['sometag']).build_input_mail()
- self.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail)
- all_tags = self.get_tags(skipDefaultTags=["true"])
+ d = self.get_tags(skipDefaultTags=["true"])
- all_tag_names = [t['name'] for t in all_tags]
- self.assertEqual(1, len(all_tag_names))
- self.assertTrue('sometag' in all_tag_names)
+ def _assert(all_tags):
+ all_tag_names = [t['name'] for t in all_tags]
+ self.assertEqual(1, len(all_tag_names))
+ self.assertTrue('sometag' in all_tag_names)
+ d.addCallback(_assert)
+ return d
+ @deferred(timeout=SoledadTestBase.DEFERRED_TIMEOUT_LONG)
def test_tags_count(self):
- self.add_multiple_to_mailbox(num=10, mailbox='inbox', flags=['\\Recent'])
- self.add_multiple_to_mailbox(num=5, mailbox='inbox', flags=['\\Seen'])
- self.add_multiple_to_mailbox(num=3, mailbox='inbox', flags=['\\Recent'], tags=['important', 'later'])
- self.add_multiple_to_mailbox(num=1, mailbox='inbox', flags=['\\Seen'], tags=['important'])
+ self.client.add_multiple_to_mailbox(num=10, mailbox='inbox', flags=['\\Recent'])
+ self.client.add_multiple_to_mailbox(num=5, mailbox='inbox', flags=['\\Seen'])
+ self.client.add_multiple_to_mailbox(num=3, mailbox='inbox', flags=['\\Recent'], tags=['important', 'later'])
+ self.client.add_multiple_to_mailbox(num=1, mailbox='inbox', flags=['\\Seen'], tags=['important'])
- tags_count = self.get_tags()
+ d = self.get_tags()
- self.assertEqual(self.get_count(tags_count, 'inbox')['total'], 19)
- self.assertEqual(self.get_count(tags_count, 'inbox')['read'], 6)
- self.assertEqual(self.get_count(tags_count, 'important')['total'], 4)
- self.assertEqual(self.get_count(tags_count, 'important')['read'], 1)
+ def _assert(tags_count):
+ self.assertEqual(self.get_count(tags_count, 'inbox')['total'], 19)
+ self.assertEqual(self.get_count(tags_count, 'inbox')['read'], 6)
+ self.assertEqual(self.get_count(tags_count, 'important')['total'], 4)
+ self.assertEqual(self.get_count(tags_count, 'important')['read'], 1)
+ d.addCallback(_assert)
+ return d
def test_search_mails_different_window(self):
input_mail = MailBuilder().build_input_mail()
input_mail2 = MailBuilder().build_input_mail()
- self.add_mail_to_inbox(input_mail)
- self.add_mail_to_inbox(input_mail2)
+ self.client.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail2)
first_page = self.get_mails_by_tag('inbox', page=1, window=1)
@@ -87,8 +101,8 @@ class SearchTest(unittest.TestCase, SoledadTestBase):
def test_search_mails_with_multiple_pages(self):
input_mail = MailBuilder().build_input_mail()
input_mail2 = MailBuilder().build_input_mail()
- self.add_mail_to_inbox(input_mail)
- self.add_mail_to_inbox(input_mail2)
+ self.client.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail2)
first_page = self.get_mails_by_tag('inbox', page=1, window=1)
second_page = self.get_mails_by_tag('inbox', page=2, window=1)
@@ -100,7 +114,7 @@ class SearchTest(unittest.TestCase, SoledadTestBase):
def test_page_zero_fetches_first_page(self):
input_mail = MailBuilder().build_input_mail()
- self.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail)
page = self.get_mails_by_tag('inbox', page=0, window=1)
self.assertEqual(page[0].ident, input_mail.ident)
@@ -113,8 +127,8 @@ class SearchTest(unittest.TestCase, SoledadTestBase):
input_mail = MailBuilder().with_date('2014-10-15T15:15').build_input_mail()
input_mail2 = MailBuilder().with_date('2014-10-15T15:16').build_input_mail()
- self.add_mail_to_inbox(input_mail)
- self.add_mail_to_inbox(input_mail2)
+ self.client.add_mail_to_inbox(input_mail)
+ self.client.add_mail_to_inbox(input_mail2)
results = self.get_mails_by_tag('inbox')
self.assertEqual(results[0].ident, input_mail2.ident)
diff --git a/service/test/integration/soledad_querier_test.py b/service/test/integration/soledad_querier_test.py
index 5d9bcccd..4a99a620 100644
--- a/service/test/integration/soledad_querier_test.py
+++ b/service/test/integration/soledad_querier_test.py
@@ -15,19 +15,21 @@
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import copy
-import unittest
import time
-from test.support.integration_helper import SoledadTestBase, MailBuilder
+
+from test.support.integration import *
from leap.mail.imap.fields import WithMsgFields
-class SoledadQuerierTest(unittest.TestCase, SoledadTestBase, WithMsgFields):
+class SoledadQuerierTest(SoledadTestBase, WithMsgFields):
def setUp(self):
- self.setup_soledad()
+ SoledadTestBase.setUp(self)
+ self.soledad = self.client.soledad
+ self.soledad_querier = self.client.soledad_querier
def tearDown(self):
- self.teardown_soledad()
+ SoledadTestBase.tearDown(self)
def _get_empty_mailbox(self):
return copy.deepcopy(self.EMPTY_MBOX)
@@ -42,7 +44,7 @@ class SoledadQuerierTest(unittest.TestCase, SoledadTestBase, WithMsgFields):
return [m for m in self.soledad.get_from_index('by-type', 'mbox') if m.content['mbox'] == mailbox_name]
def test_remove_dup_mailboxes_keeps_the_one_with_the_highest_last_uid(self):
- self.add_multiple_to_mailbox(3, 'INBOX') # by now we already have one inbox with 3 mails
+ self.client.add_multiple_to_mailbox(3, 'INBOX') # by now we already have one inbox with 3 mails
self._create_mailbox('INBOX') # now we have a duplicate
# make sure we have two
@@ -77,7 +79,7 @@ class SoledadQuerierTest(unittest.TestCase, SoledadTestBase, WithMsgFields):
self.assertEqual(1, len(mails))
def test_get_mails_by_chash(self):
- mails = self.add_multiple_to_mailbox(3, 'INBOX')
+ mails = self.client.add_multiple_to_mailbox(3, 'INBOX')
chashes = [mail.ident for mail in mails]
fetched_mails = self.soledad_querier.mails(chashes)
diff --git a/service/test/integration/tags_test.py b/service/test/integration/tags_test.py
index bd22e4b5..03d6f4b3 100644
--- a/service/test/integration/tags_test.py
+++ b/service/test/integration/tags_test.py
@@ -14,25 +14,24 @@
# 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 unittest
-from test.support.integration_helper import MailBuilder, SoledadTestBase
+from test.support.integration import *
-class TagsTest(unittest.TestCase, SoledadTestBase):
+class TagsTest(SoledadTestBase):
def setUp(self):
- self.setup_soledad()
+ SoledadTestBase.setUp(self)
def tearDown(self):
- self.teardown_soledad()
+ SoledadTestBase.tearDown(self)
def _tags_json(self, tags):
return json.dumps({'newtags': tags})
def test_add_tag_to_an_inbox_mail_and_query(self):
mail = MailBuilder().with_subject('Mail with tags').build_input_mail()
- self.add_mail_to_inbox(mail)
+ self.client.add_mail_to_inbox(mail)
self.post_tags(mail.ident, self._tags_json(['IMPORTANT']))
@@ -44,10 +43,10 @@ class TagsTest(unittest.TestCase, SoledadTestBase):
def test_addition_of_reserved_tags_is_not_allowed(self):
mail = MailBuilder().with_subject('Mail with tags').build_input_mail()
- self.add_mail_to_inbox(mail)
+ self.client.add_mail_to_inbox(mail)
response = self.post_tags(mail.ident, self._tags_json(['DRAFTS']))
self.assertEquals("None of the following words can be used as tags: drafts", response)
- mail = self.mailboxes.inbox().mail(mail.ident)
+ mail = self.client.mailboxes.inbox().mail(mail.ident)
self.assertNotIn('drafts', mail.tags)
diff --git a/service/test/support/integration/__init__.py b/service/test/support/integration/__init__.py
new file mode 100644
index 00000000..8d0bbb7a
--- /dev/null
+++ b/service/test/support/integration/__init__.py
@@ -0,0 +1,18 @@
+#
+# 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/>.
+from .app_test_client import AppTestClient
+from .model import MailBuilder, ResponseMail
+from .soledad_test_base import SoledadTestBase
diff --git a/service/test/support/integration/app_test_client.py b/service/test/support/integration/app_test_client.py
new file mode 100644
index 00000000..f6a95422
--- /dev/null
+++ b/service/test/support/integration/app_test_client.py
@@ -0,0 +1,156 @@
+#
+# 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 json
+import shutil
+
+from klein.test_resource import requestMock, _render
+from leap.mail.imap.account import SoledadBackedAccount
+from leap.soledad.client import Soledad
+from mock import MagicMock, Mock
+import os
+from pixelated.adapter.draft_service import DraftService
+from pixelated.adapter.mail_service import MailService
+from pixelated.adapter.mailboxes import Mailboxes
+from pixelated.adapter.soledad_querier import SoledadQuerier
+from pixelated.adapter.tag_service import TagService
+from pixelated.config import app_factory
+from pixelated.controllers import FeaturesController, HomeController, MailsController, TagsController, \
+ SyncInfoController, AttachmentsController
+import pixelated.runserver
+from pixelated.adapter.mail import PixelatedMail
+from pixelated.adapter.search import SearchEngine
+from test.support.integration.model import MailBuilder
+
+
+class AppTestClient:
+ def __init__(self, soledad_test_folder='soledad-test'):
+
+ self.soledad = initialize_soledad(tempdir=soledad_test_folder)
+ self.mail_address = "test@pixelated.org"
+
+ # setup app
+ PixelatedMail.from_email_address = self.mail_address
+
+ SearchEngine.INDEX_FOLDER = soledad_test_folder + '/search_index'
+
+ self.app = pixelated.runserver.app
+
+ self.soledad_querier = SoledadQuerier(self.soledad)
+ self.soledad_querier.get_index_masterkey = lambda: '_yg2oG_5ELM8_-sQYcsxI37WesI0dOtZQXpwAqjvhR4='
+
+ self.account = SoledadBackedAccount('test', self.soledad, MagicMock())
+ self.mailboxes = Mailboxes(self.account, self.soledad_querier)
+ self.mail_sender = Mock()
+ self.tag_service = TagService()
+ self.draft_service = DraftService(self.mailboxes)
+ self.mail_service = MailService(self.mailboxes, self.mail_sender, self.tag_service,
+ self.soledad_querier)
+ self.search_engine = SearchEngine(self.soledad_querier)
+ self.search_engine.index_mails(self.mail_service.all_mails())
+
+ features_controller = FeaturesController()
+ features_controller.DISABLED_FEATURES.append('autoReload')
+ home_controller = HomeController()
+ mails_controller = MailsController(mail_service=self.mail_service,
+ draft_service=self.draft_service,
+ search_engine=self.search_engine)
+ tags_controller = TagsController(search_engine=self.search_engine)
+ sync_info_controller = SyncInfoController()
+ attachments_controller = AttachmentsController(self.soledad_querier)
+
+ app_factory._setup_routes(self.app, home_controller, mails_controller, tags_controller,
+ features_controller, sync_info_controller, attachments_controller)
+
+ def _render(self, request, as_json=True):
+ def get_request_written_data(_=None):
+ written_data = request.getWrittenData()
+ if written_data:
+ return json.loads(written_data) if as_json else written_data
+
+ d = _render(self.app.resource(), request)
+ if request.finished:
+ return get_request_written_data(), request
+ else:
+ d.addCallback(get_request_written_data)
+ return d, request
+
+ def get(self, path, get_args, as_json=True):
+ request = requestMock(path)
+ request.args = get_args
+ return self._render(request, as_json)
+
+ def post(self, path, body=''):
+ request = requestMock(path=path, method="POST", body=body, headers={'Content-Type': ['application/json']})
+ return self._render(request)
+
+ def put(self, path, body):
+ request = requestMock(path=path, method="PUT", body=body, headers={'Content-Type': ['application/json']})
+ return self._render(request)
+
+ def delete(self, path):
+ request = requestMock(path=path, method="DELETE")
+ return self._render(request)
+
+ def add_document_to_soledad(self, _dict):
+ self.soledad_querier.soledad.create_doc(_dict)
+
+ def add_mail_to_inbox(self, input_mail):
+ mail = self.mailboxes.inbox().add(input_mail)
+ mail.update_tags(input_mail.tags)
+ self.search_engine.index_mail(mail)
+
+ def add_multiple_to_mailbox(self, num, mailbox='', flags=[], tags=[]):
+ mails = []
+ for _ in range(num):
+ input_mail = MailBuilder().with_status(flags).with_tags(tags).build_input_mail()
+ mail = self.mailboxes._create_or_get(mailbox).add(input_mail)
+ mails.append(mail)
+ mail.update_tags(input_mail.tags)
+ self.search_engine.index_mail(mail)
+ return mails
+
+
+def initialize_soledad(tempdir):
+ if os.path.isdir(tempdir):
+ shutil.rmtree(tempdir)
+
+ uuid = "foobar-uuid"
+ passphrase = u"verysecretpassphrase"
+ secret_path = os.path.join(tempdir, "secret.gpg")
+ local_db_path = os.path.join(tempdir, "soledad.u1db")
+ server_url = "http://provider"
+ cert_file = ""
+
+ class MockSharedDB(object):
+ get_doc = Mock(return_value=None)
+ put_doc = Mock()
+ lock = Mock(return_value=('atoken', 300))
+ unlock = Mock(return_value=True)
+ close = Mock()
+
+ def __call__(self):
+ return self
+
+ Soledad._shared_db = MockSharedDB()
+
+ _soledad = Soledad(
+ uuid,
+ passphrase,
+ secret_path,
+ local_db_path,
+ server_url,
+ cert_file)
+ return _soledad
diff --git a/service/test/support/integration/model.py b/service/test/support/integration/model.py
new file mode 100644
index 00000000..17c29c2b
--- /dev/null
+++ b/service/test/support/integration/model.py
@@ -0,0 +1,88 @@
+#
+# 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 json
+from pixelated.adapter.mail import InputMail
+from pixelated.adapter.status import Status
+
+
+class MailBuilder:
+ def __init__(self):
+ self.mail = {
+ 'header': {
+ 'to': ['recipient@to.com'],
+ 'cc': ['recipient@cc.com'],
+ 'bcc': ['recipient@bcc.com'],
+ 'subject': 'Hi! This the subject'
+ },
+ 'body': "Hello,\nThis is the body of this message\n\nRegards,\n\n--\nPixelated.\n",
+ 'status': []
+ }
+
+ def with_body(self, body):
+ self.mail['body'] = body
+ return self
+
+ def with_tags(self, tags):
+ self.mail['tags'] = tags
+ return self
+
+ def with_subject(self, subject):
+ self.mail['header']['subject'] = subject
+ return self
+
+ def with_status(self, flags):
+ for status in Status.from_flags(flags):
+ self.mail['status'].append(status)
+ return self
+
+ def with_date(self, date_string):
+ self.mail['header']['date'] = date_string
+ return self
+
+ def with_ident(self, ident):
+ self.mail['ident'] = ident
+ return self
+
+ def build_json(self):
+ return json.dumps(self.mail)
+
+ def build_input_mail(self):
+ return InputMail.from_dict(self.mail)
+
+
+class ResponseMail:
+ def __init__(self, mail_dict):
+ self.mail_dict = mail_dict
+
+ @property
+ def subject(self):
+ return self.headers['subject']
+
+ @property
+ def headers(self):
+ return self.mail_dict['header']
+
+ @property
+ def ident(self):
+ return self.mail_dict['ident']
+
+ @property
+ def tags(self):
+ return self.mail_dict['tags']
+
+ @property
+ def status(self):
+ return self.mail_dict['status']
diff --git a/service/test/support/integration/soledad_test_base.py b/service/test/support/integration/soledad_test_base.py
new file mode 100644
index 00000000..2221679f
--- /dev/null
+++ b/service/test/support/integration/soledad_test_base.py
@@ -0,0 +1,83 @@
+#
+# 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.controllers import *
+from test.support.integration.app_test_client import AppTestClient
+from test.support.integration.model import ResponseMail
+
+
+class SoledadTestBase(unittest.TestCase):
+ # these are so long because our CI is so slow at the moment.
+ DEFERRED_TIMEOUT = 120
+ DEFERRED_TIMEOUT_LONG = 300
+
+ @classmethod
+ def setUpClass(cls):
+ from nose.twistedtools import threaded_reactor
+
+ threaded_reactor()
+
+ def setUp(self):
+ self.client = AppTestClient()
+
+ def get_mails_by_tag(self, tag, page=1, window=100):
+ res, req = self.client.get("/mails", {
+ 'q': ['tag:%s' % tag],
+ 'w': [str(window)],
+ 'p': [str(page)]
+ })
+ return [ResponseMail(m) for m in res['mails']]
+
+ def post_mail(self, data):
+ res, req = self.client.post('/mails', data)
+ return ResponseMail(res)
+
+ def get_attachment(self, ident, encoding):
+ res, req = self.client.get("/attachment/%s" % ident, {'encoding': [encoding]}, as_json=False)
+ return res
+
+ def put_mail(self, data):
+ res, req = self.client.put('/mails', data)
+ return res['ident']
+
+ def post_tags(self, mail_ident, tags_json):
+ res, req = self.client.post("/mail/%s/tags" % mail_ident, tags_json)
+ return res
+
+ def get_tags(self, **kwargs):
+ res, req = self.client.get('/tags', kwargs)
+ return res
+
+ def delete_mail(self, mail_ident):
+ res, req = self.client.delete("/mail/%s" % mail_ident)
+ return req
+
+ def mark_as_read(self, mail_ident):
+ res, req = self.client.post("/mail/%s/read" % mail_ident)
+ return req
+
+ def mark_as_unread(self, mail_ident):
+ res, req = self.client.post("/mail/%s/unread" % mail_ident)
+ return req
+
+ def mark_many_as_unread(self, idents):
+ res, req = self.client.post('/mails/unread', json.dumps({'idents': idents}))
+ return req
+
+ def mark_many_as_read(self, idents):
+ res, req = self.client.post('/mails/read', json.dumps({'idents': idents}))
+ return req
diff --git a/service/test/support/integration_helper.py b/service/test/support/integration_helper.py
deleted file mode 100644
index 0411abea..00000000
--- a/service/test/support/integration_helper.py
+++ /dev/null
@@ -1,278 +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 shutil
-from klein.resource import KleinResource
-
-from leap.soledad.client import Soledad
-from mockito import mock
-import os
-from mock import Mock, MagicMock
-from pixelated.adapter.mail_service import MailService
-from pixelated.adapter.search import SearchEngine
-from pixelated.adapter.status import Status
-from pixelated.adapter.tag_service import TagService
-from pixelated.adapter.draft_service import DraftService
-from pixelated.adapter.mail import PixelatedMail, InputMail
-import pixelated.runserver
-from pixelated.adapter.mailboxes import Mailboxes
-from pixelated.adapter.soledad_querier import SoledadQuerier
-from pixelated.controllers import *
-import pixelated.config.app_factory as app_factory
-from leap.mail.imap.account import SoledadBackedAccount
-from klein.test_resource import requestMock, _render
-
-
-soledad_test_folder = "soledad-test"
-
-
-def initialize_soledad(tempdir):
- if os.path.isdir(soledad_test_folder):
- shutil.rmtree(soledad_test_folder)
-
- uuid = "foobar-uuid"
- passphrase = u"verysecretpassphrase"
- secret_path = os.path.join(tempdir, "secret.gpg")
- local_db_path = os.path.join(tempdir, "soledad.u1db")
- server_url = "http://provider"
- cert_file = ""
-
- class MockSharedDB(object):
- get_doc = Mock(return_value=None)
- put_doc = Mock()
- lock = Mock(return_value=('atoken', 300))
- unlock = Mock(return_value=True)
- close = Mock()
-
- def __call__(self):
- return self
-
- Soledad._shared_db = MockSharedDB()
-
- _soledad = Soledad(
- uuid,
- passphrase,
- secret_path,
- local_db_path,
- server_url,
- cert_file)
- #
- # from leap.mail.imap.fields import fields
- #
- # for name, expression in fields.INDEXES.items():
- # _soledad.create_index(name, *expression)
- #
- return _soledad
-
-
-class MailBuilder:
- def __init__(self):
- self.mail = {
- 'header': {
- 'to': ['recipient@to.com'],
- 'cc': ['recipient@cc.com'],
- 'bcc': ['recipient@bcc.com'],
- 'subject': 'Hi! This the subject'
- },
- 'body': "Hello,\nThis is the body of this message\n\nRegards,\n\n--\nPixelated.\n",
- 'status': []
- }
-
- def with_body(self, body):
- self.mail['body'] = body
- return self
-
- def with_tags(self, tags):
- self.mail['tags'] = tags
- return self
-
- def with_subject(self, subject):
- self.mail['header']['subject'] = subject
- return self
-
- def with_status(self, flags):
- for status in Status.from_flags(flags):
- self.mail['status'].append(status)
- return self
-
- def with_date(self, date_string):
- self.mail['header']['date'] = date_string
- return self
-
- def with_ident(self, ident):
- self.mail['ident'] = ident
- return self
-
- def build_json(self):
- return json.dumps(self.mail)
-
- def build_input_mail(self):
- return InputMail.from_dict(self.mail)
-
-
-class SoledadTestBase:
- def __init__(self):
- pass
-
- def teardown_soledad(self):
- pass
-
- def setup_soledad(self):
- self.soledad = initialize_soledad(tempdir=soledad_test_folder)
- self.mail_address = "test@pixelated.org"
-
- # setup app
- PixelatedMail.from_email_address = self.mail_address
-
- SearchEngine.INDEX_FOLDER = soledad_test_folder + '/search_index'
-
- self.app = pixelated.runserver.app
-
- self.soledad_querier = SoledadQuerier(self.soledad)
-
- self.account = SoledadBackedAccount('test', self.soledad, MagicMock())
- self.mailboxes = Mailboxes(self.account, self.soledad_querier)
- self.mail_sender = mock()
- self.tag_service = TagService()
- self.draft_service = DraftService(self.mailboxes)
- self.mail_service = MailService(self.mailboxes, self.mail_sender, self.tag_service,
- self.soledad_querier)
- self.search_engine = SearchEngine(self.soledad_querier)
- self.search_engine.index_mails(self.mail_service.all_mails())
-
- features_controller = FeaturesController()
- features_controller.DISABLED_FEATURES.append('autoReload')
- home_controller = HomeController()
- mails_controller = MailsController(mail_service=self.mail_service,
- draft_service=self.draft_service,
- search_engine=self.search_engine)
- tags_controller = TagsController(search_engine=self.search_engine)
- sync_info_controller = SyncInfoController()
- attachments_controller = AttachmentsController(self.soledad_querier)
-
- app_factory._setup_routes(self.app, home_controller, mails_controller, tags_controller,
- features_controller, sync_info_controller, attachments_controller)
- self.resource = KleinResource(self.app)
-
- def get_mails_by_tag(self, tag, page=1, window=100):
- request = requestMock(path="/mails")
- request.args = {
- 'q': ['tag:%s' % tag],
- 'w': [str(window)],
- 'p': [str(page)]
- }
- _render(self.resource, request)
- response = json.loads(request.getWrittenData())
- return [ResponseMail(m) for m in response['mails']]
-
- def post_mail(self, data):
- request = requestMock(path='/mails', method="POST", body=data, headers={'Content-Type': ['application/json']})
- _render(self.resource, request)
- response = json.loads(request.getWrittenData())
- return ResponseMail(response)
-
- def get_attachment(self, ident, encoding):
- request = requestMock(path='/attachment/' + ident)
- request.args = {
- 'encoding': [encoding]
- }
- _render(self.resource, request)
- return request.getWrittenData()
-
- def put_mail(self, data):
- request = requestMock('/mails', method="PUT", body=data, headers={'Content-Type': ['application/json']})
- _render(self.resource, request)
- response = json.loads(request.getWrittenData())
- return response['ident']
-
- def post_tags(self, mail_ident, tags_json):
- request = requestMock('/mail/' + mail_ident + '/tags', method="POST", body=tags_json,
- headers={'Content-Type': ['application/json']})
- _render(self.resource, request)
- return json.loads(request.getWrittenData())
-
- def get_tags(self, **kwargs):
- request = requestMock('/tags')
- request.args = kwargs
- _render(self.resource, request)
- return json.loads(request.getWrittenData())
-
- def delete_mail(self, mail_ident):
- request = requestMock(path='/mail/' + mail_ident, method="DELETE")
- _render(self.resource, request)
- return request
-
- def mark_as_read(self, mail_ident):
- request = requestMock('/mail/' + mail_ident + '/read', method="POST", headers={'Content-Type': ['application/json']})
- _render(self.resource, request)
- return request
-
- def mark_as_unread(self, mail_ident):
- request = requestMock('/mail/' + mail_ident + '/unread', method="POST", headers={'Content-Type': ['application/json']})
- _render(self.resource, request)
- return request
-
- def mark_many_as_unread(self, idents):
- request = requestMock('/mails/unread', method="POST", body=json.dumps({'idents': idents}), headers={'Content-Type': ['application/json']})
- _render(self.resource, request)
- return request
-
- def mark_many_as_read(self, idents):
- request = requestMock('/mails/read', method="POST", body=json.dumps({'idents': idents}), headers={'Content-Type': ['application/json']})
- _render(self.resource, request)
- return request
-
- def add_document_to_soledad(self, _dict):
- self.soledad_querier.soledad.create_doc(_dict)
-
- def add_mail_to_inbox(self, input_mail):
- mail = self.mailboxes.inbox().add(input_mail)
- mail.update_tags(input_mail.tags)
- self.search_engine.index_mail(mail)
-
- def add_multiple_to_mailbox(self, num, mailbox='', flags=[], tags=[]):
- mails = []
- for _ in range(num):
- input_mail = MailBuilder().with_status(flags).with_tags(tags).build_input_mail()
- mail = self.mailboxes._create_or_get(mailbox).add(input_mail)
- mails.append(mail)
- mail.update_tags(input_mail.tags)
- self.search_engine.index_mail(mail)
- return mails
-
-
-class ResponseMail:
- def __init__(self, mail_dict):
- self.mail_dict = mail_dict
-
- @property
- def subject(self):
- return self.headers['subject']
-
- @property
- def headers(self):
- return self.mail_dict['header']
-
- @property
- def ident(self):
- return self.mail_dict['ident']
-
- @property
- def tags(self):
- return self.mail_dict['tags']
-
- @property
- def status(self):
- return self.mail_dict['status']
diff --git a/service/test/unit/bitmask_libraries/smtp_test.py b/service/test/unit/bitmask_libraries/smtp_test.py
index 2bb3dcab..ca8a89e4 100644
--- a/service/test/unit/bitmask_libraries/smtp_test.py
+++ b/service/test/unit/bitmask_libraries/smtp_test.py
@@ -14,6 +14,7 @@
# 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
+import sys
from mock import MagicMock, patch
from abstract_leap_test import AbstractLeapTest
from pixelated.bitmask_libraries.smtp import LeapSmtp
diff --git a/web-ui/app/js/mail_list/ui/mail_items/mail_item.js b/web-ui/app/js/mail_list/ui/mail_items/mail_item.js
index 51ace714..24c7b043 100644
--- a/web-ui/app/js/mail_list/ui/mail_items/mail_item.js
+++ b/web-ui/app/js/mail_list/ui/mail_items/mail_item.js
@@ -64,6 +64,7 @@ define(
this.attr.ident = mail.ident;
this.attr.statuses = viewHelper.formatStatusClasses(mail.status);
this.attr.tags = mail.tags;
+ this.attr.attachments = mail.attachments;
this.attr.mailbox = mail.mailbox;
this.attr.header.formattedDate = this.formattedDate(mail.header.date);
};
diff --git a/web-ui/app/js/mail_view/ui/recipients/recipients_input.js b/web-ui/app/js/mail_view/ui/recipients/recipients_input.js
index e9211d85..8f647d01 100644
--- a/web-ui/app/js/mail_view/ui/recipients/recipients_input.js
+++ b/web-ui/app/js/mail_view/ui/recipients/recipients_input.js
@@ -34,7 +34,6 @@ define([
9: 'tab',
186: 'semicolon',
188: 'comma',
- 32: 'space',
13: 'enter',
27: 'esc'
},
@@ -101,7 +100,7 @@ define([
return;
}
- if (isEnterAddressKey(keyPressed)) {
+ if (!event.shiftKey && isEnterAddressKey(keyPressed)) {
this.tokenizeRecipient(event);
if ((keyPressed !== 9 /* tab */)) {
diff --git a/web-ui/app/scss/styles.scss b/web-ui/app/scss/styles.scss
index ef3691f0..7fe0ec05 100644
--- a/web-ui/app/scss/styles.scss
+++ b/web-ui/app/scss/styles.scss
@@ -133,6 +133,11 @@
right: 10px;
font-size: 0.7em;
}
+
+ .attachment-indicator {
+ margin: 2px 0 0 25px;
+ font-size: initial;
+ }
.from {
white-space: nowrap;
font-size: 0.8em;
diff --git a/web-ui/app/templates/mails/full_view.hbs b/web-ui/app/templates/mails/full_view.hbs
index 4cdf25b9..d05537f3 100644
--- a/web-ui/app/templates/mails/full_view.hbs
+++ b/web-ui/app/templates/mails/full_view.hbs
@@ -69,7 +69,7 @@
{{#if attachments}}
<div class="attachmentsAreaWrap">
<div class="attachmentsArea column large-12">
- <p><strong> {{ attachments.length }} attachment(s):</strong></p>
+ <p><strong><i class="fa fa-paperclip"></i> {{ attachments.length }} attachment(s):</strong></p>
<ul>
{{#each attachments }}
<li>
diff --git a/web-ui/app/templates/mails/single.hbs b/web-ui/app/templates/mails/single.hbs
index baf6251c..997ab44f 100644
--- a/web-ui/app/templates/mails/single.hbs
+++ b/web-ui/app/templates/mails/single.hbs
@@ -3,8 +3,13 @@
</span>
<span>
<a href="/#/{{ tag }}/mail/{{ ident }}">
- <span class="received-date">{{ header.formattedDate }}</span>
-
+ <span class="received-date">{{ header.formattedDate }}
+ {{#if attachments}}
+ <div class="attachment-indicator">
+ <i class="fa fa-paperclip"></i>
+ </div>
+ {{/if}}
+ </span>
<div class="from">{{#if header.from }}{{ header.from }}{{else}}{{t "you"}}{{/if}}</div>
<div class="subject-and-tags">
{{ header.subject }}
diff --git a/web-ui/test/spec/mail_list/ui/mail_list.spec.js b/web-ui/test/spec/mail_list/ui/mail_list.spec.js
index d90f906c..23ca87fb 100644
--- a/web-ui/test/spec/mail_list/ui/mail_list.spec.js
+++ b/web-ui/test/spec/mail_list/ui/mail_list.spec.js
@@ -225,7 +225,7 @@ describeComponent('mail_list/ui/mail_list', function () {
expect(node.html()).toMatch('id="mail-' + mail.ident + '"');
expect(node.html()).toMatch('<div class="subject-and-tags">');
expect(node.html()).toMatch('<div class="from">' + mail.header.from + '</div>');
- expect(node.html()).toMatch('<span class="received-date">' + mail.header.formattedDate + '</span>');
+ expect(node.html()).toMatch('<span class="received-date">' + mail.header.formattedDate);
}
function matchSelectedMail(mail, node) {
diff --git a/web-ui/test/spec/mail_view/ui/recipients/recipients_input.spec.js b/web-ui/test/spec/mail_view/ui/recipients/recipients_input.spec.js
index 6b486fa9..433c145b 100644
--- a/web-ui/test/spec/mail_view/ui/recipients/recipients_input.spec.js
+++ b/web-ui/test/spec/mail_view/ui/recipients/recipients_input.spec.js
@@ -10,7 +10,6 @@ describeComponent('mail_view/ui/recipients/recipients_input',function () {
_.each([
[186, 'semicolon'],
[188, 'comma'],
- [32, 'space']
], function (keycode) {
@@ -34,6 +33,16 @@ describeComponent('mail_view/ui/recipients/recipients_input',function () {
expect(addressEnteredEvent).not.toHaveBeenTriggeredOnAndWith(this, { name: 'to', address: '' });
});
+ it('wont add address if shift key is pressed together: ' + keycode[1], function () {
+ var addressEnteredEvent = spyOnEvent(this.$node, Pixelated.events.ui.recipients.entered);
+
+ var enterAddressKeyPressEvent = $.Event('keydown', { which: keycode[0], shiftKey: true });
+ this.$node.val('a@b.c');
+ this.$node.trigger(enterAddressKeyPressEvent);
+
+ expect(addressEnteredEvent).not.toHaveBeenTriggeredOnAndWith(this, { name: 'to', address: 'a@b.c' });
+ });
+
it('prevents event default regardless on input val when key is ' + keycode[1], function () {
var enterAddressKeyPressEvent = $.Event('keydown', { which: keycode[0] });
spyOn(enterAddressKeyPressEvent, 'preventDefault');
@@ -77,6 +86,21 @@ describeComponent('mail_view/ui/recipients/recipients_input',function () {
expect(addressEnteredEvent).not.toHaveBeenTriggeredOnAndWith(this, { name: 'to', address: ''});
});
});
+
+ describe('when space is pressed', function () {
+ it('address input should not finish', function () {
+ var addressEnteredEvent = spyOnEvent(this.$node, Pixelated.events.ui.recipients.entered);
+
+ var spaceKeyPressEvent = $.Event('keydown', { which: 32});
+ spyOn(spaceKeyPressEvent, 'preventDefault');
+
+ this.$node.val('a@b.c');
+ this.$node.trigger(spaceKeyPressEvent);
+
+ expect(spaceKeyPressEvent.preventDefault).not.toHaveBeenCalled();
+ expect(addressEnteredEvent).not.toHaveBeenTriggeredOnAndWith(this, { name: 'to', address: 'a@b.c' });
+ });
+ });
});
describe('on keyup', function () {