diff options
author | Dylan Baker <baker.dylan.c@gmail.com> | 2017-08-30 10:53:19 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-30 10:53:19 -0700 |
commit | 0a7bf658fdcf906f75bf29c44ef05da65fd1c053 (patch) | |
tree | e65eb1bcd9ce33e114272c47992870b1e33ff21c | |
parent | 9bd32e5b0b253aa80c44e7d54f15e66ceb84a9e8 (diff) | |
parent | e16e846db354e5775c69891c2b58b12e110f79b8 (diff) |
Merge pull request #1136 from dcbaker/submit/more-db-utils-tests
more tests + cleanups
-rw-r--r-- | alot/crypto.py | 41 | ||||
-rw-r--r-- | alot/db/utils.py | 29 | ||||
-rw-r--r-- | tests/db/utils_test.py | 168 |
3 files changed, 199 insertions, 39 deletions
diff --git a/alot/crypto.py b/alot/crypto.py index 52eb8c58..f3dd6d11 100644 --- a/alot/crypto.py +++ b/alot/crypto.py @@ -81,31 +81,22 @@ def get_key(keyid, validate=False, encrypt=False, sign=False, valid_key = None - # Catching exceptions for list_keys - try: - for k in list_keys(hint=keyid): - try: - validate_key(k, encrypt=encrypt, sign=sign) - except GPGProblem: - # if the key is invalid for given action skip it - continue - - if valid_key: - # we have already found one valid key and now we find - # another? We really received an ambiguous keyid - raise GPGProblem( - "More than one key found matching this filter. " - "Please be more specific " - "(use a key ID like 4AC8EE1D).", - code=GPGCode.AMBIGUOUS_NAME) - valid_key = k - except gpg.errors.GPGMEError as e: - # This if will be triggered if there is no key matching at all. - if e.getcode() == gpg.errors.AMBIGUOUS_NAME: + for k in list_keys(hint=keyid): + try: + validate_key(k, encrypt=encrypt, sign=sign) + except GPGProblem: + # if the key is invalid for given action skip it + continue + + if valid_key: + # we have already found one valid key and now we find + # another? We really received an ambiguous keyid raise GPGProblem( - 'Can not find any key for "{}".'.format(keyid), - code=GPGCode.NOT_FOUND) - raise + "More than one key found matching this filter. " + "Please be more specific " + "(use a key ID like 4AC8EE1D).", + code=GPGCode.AMBIGUOUS_NAME) + valid_key = k if not valid_key: # there were multiple keys found but none of them are valid for @@ -120,7 +111,7 @@ def get_key(keyid, validate=False, encrypt=False, sign=False, 'Can not find usable key for "{}".'.format(keyid), code=GPGCode.NOT_FOUND) else: - raise e + raise e # pragma: nocover if signed_only and not check_uid_validity(key, keyid): raise GPGProblem('Cannot find a trusworthy key for "{}".'.format(keyid), code=GPGCode.NOT_FOUND) diff --git a/alot/db/utils.py b/alot/db/utils.py index 488e0ed7..34f86991 100644 --- a/alot/db/utils.py +++ b/alot/db/utils.py @@ -45,6 +45,8 @@ def add_signature_headers(mail, sigs, error_msg): string indicating no error ''' sig_from = u'' + sig_known = True + uid_trusted = False if isinstance(error_msg, str): error_msg = error_msg.decode('utf-8') @@ -60,13 +62,11 @@ def add_signature_headers(mail, sigs, error_msg): uid_trusted = True break else: - # No trusted uid found, we did not break but drop from the - # for loop. - uid_trusted = False + # No trusted uid found, since we did not break from the loop. sig_from = key.uids[0].uid.decode('utf-8') - except: + except GPGProblem: sig_from = sigs[0].fpr.decode('utf-8') - uid_trusted = False + sig_known = False if error_msg: msg = u'Invalid: {}'.format(error_msg) @@ -75,7 +75,8 @@ def add_signature_headers(mail, sigs, error_msg): else: msg = u'Untrusted: {}'.format(sig_from) - mail.add_header(X_SIGNATURE_VALID_HEADER, 'False' if error_msg else 'True') + mail.add_header(X_SIGNATURE_VALID_HEADER, + 'False' if (error_msg or not sig_known) else 'True') mail.add_header(X_SIGNATURE_MESSAGE_HEADER, msg) @@ -297,16 +298,20 @@ def extract_headers(mail, headers=None): def extract_body(mail, types=None, field_key='copiousoutput'): - """ - returns a body text string for given mail. - If types is `None`, `text/*` is used: - The exact preferred type is specified by the prefer_plaintext config option - which defaults to text/html. + """Returns a string view of a Message. + + If the `types` argument is set then any encoding types there will be used + as the prefered encoding to extract. If `types` is None then + :ref:`prefer_plaintext <prefer-plaintext>` will be consulted; if it is True + then text/plain parts will be returned, if it is false then text/html will + be returned if present or text/plain if there are no text/html parts. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string - :type types: list of str + :type types: list[str] + :returns: The combined text of any parts to be used + :rtype: str """ preferred = 'text/plain' if settings.get( diff --git a/tests/db/utils_test.py b/tests/db/utils_test.py index 26768597..3e7ef9d3 100644 --- a/tests/db/utils_test.py +++ b/tests/db/utils_test.py @@ -1,5 +1,6 @@ # encoding: utf-8 # Copyright (C) 2017 Lucas Hoffmann +# Copyright © 2017 Dylan Baker # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file from __future__ import absolute_import @@ -21,6 +22,7 @@ import mock from alot import crypto from alot import helper from alot.db import utils +from alot.errors import GPGProblem from ..utilities import make_key, make_uid, TestCaseClassCleanup @@ -342,14 +344,14 @@ class TestAddSignatureHeaders(unittest.TestCase): def add_header(self, header, value): self.headers.append((header, value)) - def check(self, key, valid): + def check(self, key, valid, error_msg=u''): mail = self.FakeMail() with mock.patch('alot.db.utils.crypto.get_key', mock.Mock(return_value=key)), \ mock.patch('alot.db.utils.crypto.check_uid_validity', mock.Mock(return_value=valid)): - utils.add_signature_headers(mail, [mock.Mock(fpr='')], u'') + utils.add_signature_headers(mail, [mock.Mock(fpr='')], error_msg) return mail @@ -388,6 +390,30 @@ class TestAddSignatureHeaders(unittest.TestCase): self.assertIn( (utils.X_SIGNATURE_MESSAGE_HEADER, u'Valid: Andreá'), mail.headers) + def test_error_message_unicode(self): + mail = self.check(mock.Mock(), mock.Mock(), u'error message') + self.assertIn((utils.X_SIGNATURE_VALID_HEADER, u'False'), mail.headers) + self.assertIn( + (utils.X_SIGNATURE_MESSAGE_HEADER, u'Invalid: error message'), + mail.headers) + + def test_error_message_bytes(self): + mail = self.check(mock.Mock(), mock.Mock(), b'error message') + self.assertIn((utils.X_SIGNATURE_VALID_HEADER, u'False'), mail.headers) + self.assertIn( + (utils.X_SIGNATURE_MESSAGE_HEADER, u'Invalid: error message'), + mail.headers) + + def test_get_key_fails(self): + mail = self.FakeMail() + with mock.patch('alot.db.utils.crypto.get_key', + mock.Mock(side_effect=GPGProblem(u'', 0))): + utils.add_signature_headers(mail, [mock.Mock(fpr='')], u'') + self.assertIn((utils.X_SIGNATURE_VALID_HEADER, u'False'), mail.headers) + self.assertIn( + (utils.X_SIGNATURE_MESSAGE_HEADER, u'Untrusted: '), + mail.headers) + class TestMessageFromFile(TestCaseClassCleanup): @@ -625,3 +651,141 @@ class TestMessageFromFile(TestCaseClassCleanup): self.assertIn('This is some text', [n.get_payload() for n in m.walk()]) self.assertIn(utils.X_SIGNATURE_VALID_HEADER, m) self.assertIn(utils.X_SIGNATURE_MESSAGE_HEADER, m) + + +class TestExtractBody(unittest.TestCase): + + @staticmethod + def _set_basic_headers(mail): + mail['Subject'] = 'Test email' + mail['To'] = 'foo@example.com' + mail['From'] = 'bar@example.com' + + def test_single_text_plain(self): + mail = email.mime.text.MIMEText('This is an email') + self._set_basic_headers(mail) + actual = utils.extract_body(mail) + + expected = 'This is an email' + + self.assertEqual(actual, expected) + + def test_two_text_plain(self): + mail = email.mime.multipart.MIMEMultipart() + self._set_basic_headers(mail) + mail.attach(email.mime.text.MIMEText('This is an email')) + mail.attach(email.mime.text.MIMEText('This is a second part')) + + actual = utils.extract_body(mail) + expected = 'This is an email\n\nThis is a second part' + + self.assertEqual(actual, expected) + + def test_text_plain_and_other(self): + mail = email.mime.multipart.MIMEMultipart() + self._set_basic_headers(mail) + mail.attach(email.mime.text.MIMEText('This is an email')) + mail.attach(email.mime.application.MIMEApplication(b'1')) + + actual = utils.extract_body(mail) + expected = 'This is an email' + + self.assertEqual(actual, expected) + + def test_text_plain_with_attachment_text(self): + mail = email.mime.multipart.MIMEMultipart() + self._set_basic_headers(mail) + mail.attach(email.mime.text.MIMEText('This is an email')) + attachment = email.mime.text.MIMEText('this shouldnt be displayed') + attachment['Content-Disposition'] = 'attachment' + mail.attach(attachment) + + actual = utils.extract_body(mail) + expected = 'This is an email' + + self.assertEqual(actual, expected) + + def _make_mixed_plain_html(self): + mail = email.mime.multipart.MIMEMultipart() + self._set_basic_headers(mail) + mail.attach(email.mime.text.MIMEText('This is an email')) + mail.attach(email.mime.text.MIMEText( + '<!DOCTYPE html><html><body>This is an html email</body></html>', + 'html')) + return mail + + @mock.patch('alot.db.utils.settings.get', mock.Mock(return_value=True)) + def test_prefer_plaintext(self): + expected = 'This is an email' + mail = self._make_mixed_plain_html() + actual = utils.extract_body(mail) + + self.assertEqual(actual, expected) + + # Mock the handler to cat, so that no transformations of the html are made + # making the result non-deterministic + @mock.patch('alot.db.utils.settings.get', mock.Mock(return_value=False)) + @mock.patch('alot.db.utils.settings.mailcap_find_match', + mock.Mock(return_value=(None, {'view': 'cat'}))) + def test_prefer_html(self): + expected = '<!DOCTYPE html><html><body>This is an html email</body></html>' + mail = self._make_mixed_plain_html() + actual = utils.extract_body(mail) + + self.assertEqual(actual, expected) + + @mock.patch('alot.db.utils.settings.get', mock.Mock(return_value=False)) + @mock.patch('alot.db.utils.settings.mailcap_find_match', + mock.Mock(return_value=(None, {'view': 'cat'}))) + def test_types_provided(self): + # This should not return html, even though html is set to preferred + # since a types variable is passed + expected = 'This is an email' + mail = self._make_mixed_plain_html() + actual = utils.extract_body(mail, types=['text/plain']) + + self.assertEqual(actual, expected) + + @mock.patch('alot.db.utils.settings.mailcap_find_match', + mock.Mock(return_value=(None, {'view': 'cat'}))) + def test_require_mailcap_stdin(self): + mail = email.mime.multipart.MIMEMultipart() + self._set_basic_headers(mail) + mail.attach(email.mime.text.MIMEText( + '<!DOCTYPE html><html><body>This is an html email</body></html>', + 'html')) + actual = utils.extract_body(mail) + expected = '<!DOCTYPE html><html><body>This is an html email</body></html>' + + self.assertEqual(actual, expected) + + @mock.patch('alot.db.utils.settings.mailcap_find_match', + mock.Mock(return_value=(None, {'view': 'cat %s'}))) + def test_require_mailcap_file(self): + mail = email.mime.multipart.MIMEMultipart() + self._set_basic_headers(mail) + mail.attach(email.mime.text.MIMEText( + '<!DOCTYPE html><html><body>This is an html email</body></html>', + 'html')) + actual = utils.extract_body(mail) + expected = '<!DOCTYPE html><html><body>This is an html email</body></html>' + + self.assertEqual(actual, expected) + + +class TestMessageFromString(unittest.TestCase): + + """Tests for message_from_string. + + Because the implementation is that this is a wrapper around + message_from_file, it's not important to have a large swath of tests, just + enough to show that things are being passed correctly. + """ + + def test(self): + m = email.mime.text.MIMEText(u'This is some text', 'plain', 'utf-8') + m['Subject'] = 'test' + m['From'] = 'me' + m['To'] = 'Nobody' + message = utils.message_from_string(m.as_string()) + self.assertEqual(message.get_payload(), 'This is some text') |