From 7f0a1b8cdd492917f84f90cade28cefa0a37c3e0 Mon Sep 17 00:00:00 2001 From: Lucas Hoffmann Date: Fri, 21 Dec 2018 00:33:19 +0100 Subject: Rename test files The two main reasons are - to run `python3 -m unittest discover` without specifying a custom `--pattern *_test.py` - to include the test files automatically when generating the MANIFEST file. --- tests/commands/envelope_test.py | 386 ---------------------------------------- tests/commands/global_test.py | 246 ------------------------- tests/commands/init_test.py | 50 ------ tests/commands/test_envelope.py | 386 ++++++++++++++++++++++++++++++++++++++++ tests/commands/test_global.py | 246 +++++++++++++++++++++++++ tests/commands/test_init.py | 50 ++++++ tests/commands/test_thread.py | 230 ++++++++++++++++++++++++ tests/commands/thread_test.py | 230 ------------------------ 8 files changed, 912 insertions(+), 912 deletions(-) delete mode 100644 tests/commands/envelope_test.py delete mode 100644 tests/commands/global_test.py delete mode 100644 tests/commands/init_test.py create mode 100644 tests/commands/test_envelope.py create mode 100644 tests/commands/test_global.py create mode 100644 tests/commands/test_init.py create mode 100644 tests/commands/test_thread.py delete mode 100644 tests/commands/thread_test.py (limited to 'tests/commands') diff --git a/tests/commands/envelope_test.py b/tests/commands/envelope_test.py deleted file mode 100644 index 76aa7e88..00000000 --- a/tests/commands/envelope_test.py +++ /dev/null @@ -1,386 +0,0 @@ -# encoding=utf-8 -# Copyright © 2017-2018 Dylan Baker - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program 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 General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Tests for the alot.commands.envelope module.""" - -import email -import os -import tempfile -import textwrap -import unittest - -import mock - -from alot.commands import envelope -from alot.db.envelope import Envelope -from alot.errors import GPGProblem -from alot.settings.errors import NoMatchingAccount -from alot.settings.manager import SettingsManager -from alot.account import Account - -from .. import utilities - -# When using an assert from a mock a TestCase method might not use self. That's -# okay. -# pylint: disable=no-self-use - - -class TestAttachCommand(unittest.TestCase): - """Tests for the AttachCommaned class.""" - - def test_single_path(self): - """A test for an existing single path.""" - ui = utilities.make_ui() - - with tempfile.TemporaryDirectory() as d: - testfile = os.path.join(d, 'foo') - with open(testfile, 'w') as f: - f.write('foo') - - cmd = envelope.AttachCommand(path=testfile) - cmd.apply(ui) - ui.current_buffer.envelope.attach.assert_called_with(testfile) - - def test_user(self): - """A test for an existing single path prefaced with ~/.""" - ui = utilities.make_ui() - - with tempfile.TemporaryDirectory() as d: - # This mock replaces expanduser to replace "~/" with a path to the - # temporary directory. This is easier and more reliable than - # relying on changing an environment variable (like HOME), since it - # doesn't rely on CPython implementation details. - with mock.patch('alot.commands.os.path.expanduser', - lambda x: os.path.join(d, x[2:])): - testfile = os.path.join(d, 'foo') - with open(testfile, 'w') as f: - f.write('foo') - - cmd = envelope.AttachCommand(path='~/foo') - cmd.apply(ui) - ui.current_buffer.envelope.attach.assert_called_with(testfile) - - def test_glob(self): - """A test using a glob.""" - ui = utilities.make_ui() - - with tempfile.TemporaryDirectory() as d: - testfile1 = os.path.join(d, 'foo') - testfile2 = os.path.join(d, 'far') - for t in [testfile1, testfile2]: - with open(t, 'w') as f: - f.write('foo') - - cmd = envelope.AttachCommand(path=os.path.join(d, '*')) - cmd.apply(ui) - ui.current_buffer.envelope.attach.assert_has_calls( - [mock.call(testfile1), mock.call(testfile2)], any_order=True) - - def test_no_match(self): - """A test for a file that doesn't exist.""" - ui = utilities.make_ui() - - with tempfile.TemporaryDirectory() as d: - cmd = envelope.AttachCommand(path=os.path.join(d, 'doesnt-exist')) - cmd.apply(ui) - ui.notify.assert_called() - - -class TestTagCommands(unittest.TestCase): - - def _test(self, tagstring, action, expected): - """Common steps for envelope.TagCommand tests - - :param tagstring: the string to pass to the TagCommand - :type tagstring: str - :param action: the action to pass to the TagCommand - :type action: str - :param expected: the expected output to assert in the test - :type expected: list(str) - """ - env = Envelope(tags=['one', 'two', 'three']) - ui = utilities.make_ui() - ui.current_buffer = mock.Mock() - ui.current_buffer.envelope = env - cmd = envelope.TagCommand(tags=tagstring, action=action) - cmd.apply(ui) - actual = env.tags - self.assertListEqual(sorted(actual), sorted(expected)) - - def test_add_new_tags(self): - self._test(u'four', 'add', ['one', 'two', 'three', 'four']) - - def test_adding_existing_tags_has_no_effect(self): - self._test(u'one', 'add', ['one', 'two', 'three']) - - def test_remove_existing_tags(self): - self._test(u'one', 'remove', ['two', 'three']) - - def test_remove_non_existing_tags_has_no_effect(self): - self._test(u'four', 'remove', ['one', 'two', 'three']) - - def test_set_tags(self): - self._test(u'a,b,c', 'set', ['a', 'b', 'c']) - - def test_toggle_will_remove_existing_tags(self): - self._test(u'one', 'toggle', ['two', 'three']) - - def test_toggle_will_add_new_tags(self): - self._test(u'four', 'toggle', ['one', 'two', 'three', 'four']) - - def test_toggle_can_remove_and_add_in_one_run(self): - self._test(u'one,four', 'toggle', ['two', 'three', 'four']) - - -class TestSignCommand(unittest.TestCase): - - """Tests for the SignCommand class.""" - - @staticmethod - def _make_ui_mock(): - """Create a mock for the ui and envelope and return them.""" - envelope = Envelope() - envelope['From'] = 'foo ' - envelope.sign = mock.sentinel.default - envelope.sign_key = mock.sentinel.default - ui = utilities.make_ui(current_buffer=mock.Mock(envelope=envelope)) - return envelope, ui - - @mock.patch('alot.commands.envelope.crypto.get_key', - mock.Mock(return_value=mock.sentinel.keyid)) - def test_apply_keyid_success(self): - """If there is a valid keyid then key and to sign should be set. - """ - env, ui = self._make_ui_mock() - # The actual keyid doesn't matter, since it'll be mocked anyway - cmd = envelope.SignCommand(action='sign', keyid=['a']) - cmd.apply(ui) - - self.assertTrue(env.sign) - self.assertEqual(env.sign_key, mock.sentinel.keyid) - - @mock.patch('alot.commands.envelope.crypto.get_key', - mock.Mock(side_effect=GPGProblem('sentinel', 0))) - def test_apply_keyid_gpgproblem(self): - """If there is an invalid keyid then the signing key and to sign should - be set to false and default. - """ - env, ui = self._make_ui_mock() - # The actual keyid doesn't matter, since it'll be mocked anyway - cmd = envelope.SignCommand(action='sign', keyid=['a']) - cmd.apply(ui) - self.assertFalse(env.sign) - self.assertEqual(env.sign_key, mock.sentinel.default) - ui.notify.assert_called_once() - - @mock.patch('alot.commands.envelope.settings.account_matching_address', - mock.Mock(side_effect=NoMatchingAccount)) - def test_apply_no_keyid_nomatchingaccount(self): - """If there is a nokeyid and no account can be found to match the From, - then the envelope should not be marked to sign. - """ - env, ui = self._make_ui_mock() - # The actual keyid doesn't matter, since it'll be mocked anyway - cmd = envelope.SignCommand(action='sign', keyid=None) - cmd.apply(ui) - - self.assertFalse(env.sign) - self.assertEqual(env.sign_key, mock.sentinel.default) - ui.notify.assert_called_once() - - def test_apply_no_keyid_no_gpg_key(self): - """If there is a nokeyid and the account has no gpg key then the - signing key and to sign should be set to false and default. - """ - env, ui = self._make_ui_mock() - env.account = mock.Mock(gpg_key=None) - - cmd = envelope.SignCommand(action='sign', keyid=None) - cmd.apply(ui) - - self.assertFalse(env.sign) - self.assertEqual(env.sign_key, mock.sentinel.default) - ui.notify.assert_called_once() - - def test_apply_no_keyid_default(self): - """If there is no keyid and the account has a gpg key, then that should - be used. - """ - env, ui = self._make_ui_mock() - env.account = mock.Mock(gpg_key='sentinel') - - cmd = envelope.SignCommand(action='sign', keyid=None) - cmd.apply(ui) - - self.assertTrue(env.sign) - self.assertEqual(env.sign_key, 'sentinel') - - @mock.patch('alot.commands.envelope.crypto.get_key', - mock.Mock(return_value=mock.sentinel.keyid)) - def test_apply_no_sign(self): - """If signing with a valid keyid and valid key then set sign and - sign_key. - """ - env, ui = self._make_ui_mock() - # The actual keyid doesn't matter, since it'll be mocked anyway - cmd = envelope.SignCommand(action='sign', keyid=['a']) - cmd.apply(ui) - - self.assertTrue(env.sign) - self.assertEqual(env.sign_key, mock.sentinel.keyid) - - @mock.patch('alot.commands.envelope.crypto.get_key', - mock.Mock(return_value=mock.sentinel.keyid)) - def test_apply_unsign(self): - """Test that settingun sign sets the sign to False if all other - conditions allow for it. - """ - env, ui = self._make_ui_mock() - env.sign = True - env.sign_key = mock.sentinel - # The actual keyid doesn't matter, since it'll be mocked anyway - cmd = envelope.SignCommand(action='unsign', keyid=['a']) - cmd.apply(ui) - - self.assertFalse(env.sign) - self.assertIs(env.sign_key, None) - - @mock.patch('alot.commands.envelope.crypto.get_key', - mock.Mock(return_value=mock.sentinel.keyid)) - def test_apply_togglesign(self): - """Test that toggling changes the sign and sign_key as approriate if - other condtiions allow for it - """ - env, ui = self._make_ui_mock() - env.sign = True - env.sign_key = mock.sentinel.keyid - - # The actual keyid doesn't matter, since it'll be mocked anyway - # Test that togling from true to false works - cmd = envelope.SignCommand(action='toggle', keyid=['a']) - cmd.apply(ui) - self.assertFalse(env.sign) - self.assertIs(env.sign_key, None) - - # Test that toggling back to True works - cmd.apply(ui) - self.assertTrue(env.sign) - self.assertIs(env.sign_key, mock.sentinel.keyid) - - def _make_local_settings(self): - config = textwrap.dedent("""\ - [accounts] - [[default]] - realname = foo - address = foo@example.com - sendmail_command = /bin/true - """) - - # Allow settings.reload to work by not deleting the file until the end - with tempfile.NamedTemporaryFile(mode='w+', delete=False) as f: - f.write(config) - self.addCleanup(os.unlink, f.name) - - # Set the gpg_key separately to avoid validation failures - manager = SettingsManager() - manager.read_config(f.name) - manager.get_accounts()[0].gpg_key = mock.sentinel.gpg_key - return manager - - def test_apply_from_email_only(self): - """Test that a key can be derived using a 'From' header that contains - only an email. - - If the from header is in the form "foo@example.com" and a key exists it - should be used. - """ - manager = self._make_local_settings() - env, ui = self._make_ui_mock() - env.headers = {'From': ['foo@example.com']} - - cmd = envelope.SignCommand(action='sign') - with mock.patch('alot.commands.envelope.settings', manager): - cmd.apply(ui) - - self.assertTrue(env.sign) - self.assertIs(env.sign_key, mock.sentinel.gpg_key) - - def test_apply_from_user_and_email(self): - """This tests that a gpg key can be derived using a 'From' header that - contains a realname-email combo. - - If the header is in the form "Foo ", a key should be - derived. - - See issue #1113 - """ - manager = self._make_local_settings() - env, ui = self._make_ui_mock() - - cmd = envelope.SignCommand(action='sign') - with mock.patch('alot.commands.envelope.settings', manager): - cmd.apply(ui) - - self.assertTrue(env.sign) - self.assertIs(env.sign_key, mock.sentinel.gpg_key) - - -class TestSendCommand(unittest.TestCase): - - """Tests for the SendCommand class.""" - - mail = textwrap.dedent("""\ - From: foo@example.com - To: bar@example.com - Subject: FooBar - - Foo Bar Baz - """) - - class MockedAccount(Account): - - def __init__(self): - super().__init__('foo@example.com') - - async def send_mail(self, mail): - pass - - @utilities.async_test - async def test_account_matching_address_with_str(self): - cmd = envelope.SendCommand(mail=self.mail) - account = mock.Mock(wraps=self.MockedAccount()) - with mock.patch( - 'alot.commands.envelope.settings.account_matching_address', - mock.Mock(return_value=account)) as account_matching_address: - await cmd.apply(mock.Mock()) - account_matching_address.assert_called_once_with('foo@example.com', - return_default=True) - # check that the apply did run through till the end. - account.send_mail.assert_called_once_with(self.mail) - - @utilities.async_test - async def test_account_matching_address_with_email_message(self): - mail = email.message_from_string(self.mail) - cmd = envelope.SendCommand(mail=mail) - account = mock.Mock(wraps=self.MockedAccount()) - with mock.patch( - 'alot.commands.envelope.settings.account_matching_address', - mock.Mock(return_value=account)) as account_matching_address: - await cmd.apply(mock.Mock()) - account_matching_address.assert_called_once_with('foo@example.com', - return_default=True) - # check that the apply did run through till the end. - account.send_mail.assert_called_once_with(mail) diff --git a/tests/commands/global_test.py b/tests/commands/global_test.py deleted file mode 100644 index 9b026e6a..00000000 --- a/tests/commands/global_test.py +++ /dev/null @@ -1,246 +0,0 @@ -# encoding=utf-8 -# Copyright © 2017-2018 Dylan Baker -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Tests for global commands.""" - -import logging -import os -import tempfile -import unittest - -import mock - -from alot.commands import globals as g_commands - -from .. import utilities - - -class Stop(Exception): - """exception for stopping testing of giant unmanagable functions.""" - pass - - -class TestComposeCommand(unittest.TestCase): - - """Tests for the compose command.""" - - @staticmethod - def _make_envelope_mock(): - envelope = mock.Mock() - envelope.headers = {'From': 'foo '} - envelope.get = envelope.headers.get - envelope.sign_key = None - envelope.sign = False - return envelope - - @staticmethod - def _make_account_mock( - sign_by_default=True, gpg_key=mock.sentinel.gpg_key): - account = mock.Mock() - account.sign_by_default = sign_by_default - account.gpg_key = gpg_key - account.signature = None - return account - - @utilities.async_test - async def test_apply_sign_by_default_okay(self): - envelope = self._make_envelope_mock() - envelope.account = self._make_account_mock() - cmd = g_commands.ComposeCommand(envelope=envelope) - - with mock.patch('alot.commands.globals.settings.get_addressbooks', - mock.Mock(side_effect=Stop)): - try: - await cmd.apply(mock.Mock()) - except Stop: - pass - - self.assertTrue(envelope.sign) - self.assertIs(envelope.sign_key, mock.sentinel.gpg_key) - - @utilities.async_test - async def test_apply_sign_by_default_false_doesnt_set_key(self): - envelope = self._make_envelope_mock() - envelope.account = self._make_account_mock(sign_by_default=False) - cmd = g_commands.ComposeCommand(envelope=envelope) - - with mock.patch('alot.commands.globals.settings.get_addressbooks', - mock.Mock(side_effect=Stop)): - try: - await cmd.apply(mock.Mock()) - except Stop: - pass - - self.assertFalse(envelope.sign) - self.assertIs(envelope.sign_key, None) - - @utilities.async_test - async def test_apply_sign_by_default_but_no_key(self): - envelope = self._make_envelope_mock() - envelope.account = self._make_account_mock(gpg_key=None) - cmd = g_commands.ComposeCommand(envelope=envelope) - - with mock.patch('alot.commands.globals.settings.get_addressbooks', - mock.Mock(side_effect=Stop)): - try: - await cmd.apply(mock.Mock()) - except Stop: - pass - - self.assertFalse(envelope.sign) - self.assertIs(envelope.sign_key, None) - - @utilities.async_test - async def test_decode_template_on_loading(self): - subject = u'This is a täßϑ subject.' - to = u'recipient@mail.com' - _from = u'foo.bar@mail.fr' - body = u'Body\n地初店会継思識棋御招告外児山望掲領環。\n€mail body €nd.' - with tempfile.NamedTemporaryFile('wb', delete=False) as f: - txt = u'Subject: {}\nTo: {}\nFrom: {}\n{}'.format(subject, to, - _from, body) - f.write(txt.encode('utf-8')) - self.addCleanup(os.unlink, f.name) - - cmd = g_commands.ComposeCommand(template=f.name) - - # Crutch to exit the giant `apply` method early. - with mock.patch( - 'alot.commands.globals.settings.get_accounts', - mock.Mock(side_effect=Stop)): - try: - await cmd.apply(mock.Mock()) - except Stop: - pass - - self.assertEqual({'To': [to], - 'From': [_from], - 'Subject': [subject]}, cmd.envelope.headers) - self.assertEqual(body, cmd.envelope.body) - - @utilities.async_test - async def test_single_account_no_from(self): - # issue #1277 - envelope = self._make_envelope_mock() - del envelope.headers['From'] - envelope.account = self._make_account_mock() - envelope.account.realname = "foo" - envelope.account.address = 1 # maybe this should be a real Address? - cmd = g_commands.ComposeCommand(envelope=envelope) - - with mock.patch('alot.commands.globals.settings.get_addressbooks', - mock.Mock(side_effect=Stop)): - try: - await cmd.apply(mock.Mock()) - except Stop: - pass - - -class TestExternalCommand(unittest.TestCase): - - @utilities.async_test - async def test_no_spawn_no_stdin_success(self): - ui = utilities.make_ui() - cmd = g_commands.ExternalCommand(u'true', refocus=False) - await cmd.apply(ui) - ui.notify.assert_not_called() - - @utilities.async_test - async def test_no_spawn_stdin_success(self): - ui = utilities.make_ui() - cmd = g_commands.ExternalCommand(u"awk '{ exit $0 }'", stdin=u'0', - refocus=False) - await cmd.apply(ui) - ui.notify.assert_not_called() - - @utilities.async_test - async def test_no_spawn_no_stdin_attached(self): - ui = utilities.make_ui() - cmd = g_commands.ExternalCommand(u'test -t 0', refocus=False) - await cmd.apply(ui) - ui.notify.assert_not_called() - - @utilities.async_test - async def test_no_spawn_stdin_attached(self): - ui = utilities.make_ui() - cmd = g_commands.ExternalCommand( - u"test -t 0", stdin=u'0', refocus=False) - await cmd.apply(ui) - ui.notify.assert_called_once_with('', priority='error') - - @utilities.async_test - async def test_no_spawn_failure(self): - ui = utilities.make_ui() - cmd = g_commands.ExternalCommand(u'false', refocus=False) - await cmd.apply(ui) - ui.notify.assert_called_once_with('', priority='error') - - @utilities.async_test - @mock.patch( - 'alot.commands.globals.settings.get', mock.Mock(return_value='')) - @mock.patch.dict(os.environ, {'DISPLAY': ':0'}) - async def test_spawn_no_stdin_success(self): - ui = utilities.make_ui() - cmd = g_commands.ExternalCommand(u'true', refocus=False, spawn=True) - await cmd.apply(ui) - ui.notify.assert_not_called() - - @utilities.async_test - @mock.patch( - 'alot.commands.globals.settings.get', mock.Mock(return_value='')) - @mock.patch.dict(os.environ, {'DISPLAY': ':0'}) - async def test_spawn_stdin_success(self): - ui = utilities.make_ui() - cmd = g_commands.ExternalCommand( - u"awk '{ exit $0 }'", - stdin=u'0', refocus=False, spawn=True) - await cmd.apply(ui) - ui.notify.assert_not_called() - - @utilities.async_test - @mock.patch( - 'alot.commands.globals.settings.get', mock.Mock(return_value='')) - @mock.patch.dict(os.environ, {'DISPLAY': ':0'}) - async def test_spawn_failure(self): - ui = utilities.make_ui() - cmd = g_commands.ExternalCommand(u'false', refocus=False, spawn=True) - await cmd.apply(ui) - ui.notify.assert_called_once_with('', priority='error') - - -class TestCallCommand(unittest.TestCase): - - @utilities.async_test - async def test_synchronous_call(self): - ui = mock.Mock() - cmd = g_commands.CallCommand('ui()') - await cmd.apply(ui) - ui.assert_called_once() - - @utilities.async_test - async def test_async_call(self): - async def func(obj): - obj() - - ui = mock.Mock() - hooks = mock.Mock() - hooks.ui = None - hooks.func = func - - with mock.patch('alot.commands.globals.settings.hooks', hooks): - cmd = g_commands.CallCommand('hooks.func(ui)') - await cmd.apply(ui) - ui.assert_called_once() diff --git a/tests/commands/init_test.py b/tests/commands/init_test.py deleted file mode 100644 index a2b358ff..00000000 --- a/tests/commands/init_test.py +++ /dev/null @@ -1,50 +0,0 @@ -# encoding=utf-8 - -"""Test suite for alot.commands.__init__ module.""" - -import argparse -import unittest - -import mock - -from alot import commands -from alot.commands import thread - -# Good descriptive test names often don't fit PEP8, which is meant to cover -# functions meant to be called by humans. -# pylint: disable=invalid-name - -# These are tests, don't worry about names like "foo" and "bar" -# pylint: disable=blacklisted-name - - -class TestLookupCommand(unittest.TestCase): - - def test_look_up_save_attachment_command_in_thread_mode(self): - cmd, parser, kwargs = commands.lookup_command('save', 'thread') - # TODO do some more tests with these return values - self.assertEqual(cmd, thread.SaveAttachmentCommand) - self.assertIsInstance(parser, argparse.ArgumentParser) - self.assertDictEqual(kwargs, {}) - - -class TestCommandFactory(unittest.TestCase): - - def test_create_save_attachment_command_with_arguments(self): - cmd = commands.commandfactory('save --all /foo', mode='thread') - self.assertIsInstance(cmd, thread.SaveAttachmentCommand) - self.assertTrue(cmd.all) - self.assertEqual(cmd.path, u'/foo') - - -class TestRegisterCommand(unittest.TestCase): - """Tests for the registerCommand class.""" - - def test_registered(self): - """using registerCommand adds to the COMMANDS dict.""" - with mock.patch('alot.commands.COMMANDS', {'foo': {}}): - @commands.registerCommand('foo', 'test') - def foo(): # pylint: disable=unused-variable - pass - - self.assertIn('test', commands.COMMANDS['foo']) diff --git a/tests/commands/test_envelope.py b/tests/commands/test_envelope.py new file mode 100644 index 00000000..76aa7e88 --- /dev/null +++ b/tests/commands/test_envelope.py @@ -0,0 +1,386 @@ +# encoding=utf-8 +# Copyright © 2017-2018 Dylan Baker + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program 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 General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Tests for the alot.commands.envelope module.""" + +import email +import os +import tempfile +import textwrap +import unittest + +import mock + +from alot.commands import envelope +from alot.db.envelope import Envelope +from alot.errors import GPGProblem +from alot.settings.errors import NoMatchingAccount +from alot.settings.manager import SettingsManager +from alot.account import Account + +from .. import utilities + +# When using an assert from a mock a TestCase method might not use self. That's +# okay. +# pylint: disable=no-self-use + + +class TestAttachCommand(unittest.TestCase): + """Tests for the AttachCommaned class.""" + + def test_single_path(self): + """A test for an existing single path.""" + ui = utilities.make_ui() + + with tempfile.TemporaryDirectory() as d: + testfile = os.path.join(d, 'foo') + with open(testfile, 'w') as f: + f.write('foo') + + cmd = envelope.AttachCommand(path=testfile) + cmd.apply(ui) + ui.current_buffer.envelope.attach.assert_called_with(testfile) + + def test_user(self): + """A test for an existing single path prefaced with ~/.""" + ui = utilities.make_ui() + + with tempfile.TemporaryDirectory() as d: + # This mock replaces expanduser to replace "~/" with a path to the + # temporary directory. This is easier and more reliable than + # relying on changing an environment variable (like HOME), since it + # doesn't rely on CPython implementation details. + with mock.patch('alot.commands.os.path.expanduser', + lambda x: os.path.join(d, x[2:])): + testfile = os.path.join(d, 'foo') + with open(testfile, 'w') as f: + f.write('foo') + + cmd = envelope.AttachCommand(path='~/foo') + cmd.apply(ui) + ui.current_buffer.envelope.attach.assert_called_with(testfile) + + def test_glob(self): + """A test using a glob.""" + ui = utilities.make_ui() + + with tempfile.TemporaryDirectory() as d: + testfile1 = os.path.join(d, 'foo') + testfile2 = os.path.join(d, 'far') + for t in [testfile1, testfile2]: + with open(t, 'w') as f: + f.write('foo') + + cmd = envelope.AttachCommand(path=os.path.join(d, '*')) + cmd.apply(ui) + ui.current_buffer.envelope.attach.assert_has_calls( + [mock.call(testfile1), mock.call(testfile2)], any_order=True) + + def test_no_match(self): + """A test for a file that doesn't exist.""" + ui = utilities.make_ui() + + with tempfile.TemporaryDirectory() as d: + cmd = envelope.AttachCommand(path=os.path.join(d, 'doesnt-exist')) + cmd.apply(ui) + ui.notify.assert_called() + + +class TestTagCommands(unittest.TestCase): + + def _test(self, tagstring, action, expected): + """Common steps for envelope.TagCommand tests + + :param tagstring: the string to pass to the TagCommand + :type tagstring: str + :param action: the action to pass to the TagCommand + :type action: str + :param expected: the expected output to assert in the test + :type expected: list(str) + """ + env = Envelope(tags=['one', 'two', 'three']) + ui = utilities.make_ui() + ui.current_buffer = mock.Mock() + ui.current_buffer.envelope = env + cmd = envelope.TagCommand(tags=tagstring, action=action) + cmd.apply(ui) + actual = env.tags + self.assertListEqual(sorted(actual), sorted(expected)) + + def test_add_new_tags(self): + self._test(u'four', 'add', ['one', 'two', 'three', 'four']) + + def test_adding_existing_tags_has_no_effect(self): + self._test(u'one', 'add', ['one', 'two', 'three']) + + def test_remove_existing_tags(self): + self._test(u'one', 'remove', ['two', 'three']) + + def test_remove_non_existing_tags_has_no_effect(self): + self._test(u'four', 'remove', ['one', 'two', 'three']) + + def test_set_tags(self): + self._test(u'a,b,c', 'set', ['a', 'b', 'c']) + + def test_toggle_will_remove_existing_tags(self): + self._test(u'one', 'toggle', ['two', 'three']) + + def test_toggle_will_add_new_tags(self): + self._test(u'four', 'toggle', ['one', 'two', 'three', 'four']) + + def test_toggle_can_remove_and_add_in_one_run(self): + self._test(u'one,four', 'toggle', ['two', 'three', 'four']) + + +class TestSignCommand(unittest.TestCase): + + """Tests for the SignCommand class.""" + + @staticmethod + def _make_ui_mock(): + """Create a mock for the ui and envelope and return them.""" + envelope = Envelope() + envelope['From'] = 'foo ' + envelope.sign = mock.sentinel.default + envelope.sign_key = mock.sentinel.default + ui = utilities.make_ui(current_buffer=mock.Mock(envelope=envelope)) + return envelope, ui + + @mock.patch('alot.commands.envelope.crypto.get_key', + mock.Mock(return_value=mock.sentinel.keyid)) + def test_apply_keyid_success(self): + """If there is a valid keyid then key and to sign should be set. + """ + env, ui = self._make_ui_mock() + # The actual keyid doesn't matter, since it'll be mocked anyway + cmd = envelope.SignCommand(action='sign', keyid=['a']) + cmd.apply(ui) + + self.assertTrue(env.sign) + self.assertEqual(env.sign_key, mock.sentinel.keyid) + + @mock.patch('alot.commands.envelope.crypto.get_key', + mock.Mock(side_effect=GPGProblem('sentinel', 0))) + def test_apply_keyid_gpgproblem(self): + """If there is an invalid keyid then the signing key and to sign should + be set to false and default. + """ + env, ui = self._make_ui_mock() + # The actual keyid doesn't matter, since it'll be mocked anyway + cmd = envelope.SignCommand(action='sign', keyid=['a']) + cmd.apply(ui) + self.assertFalse(env.sign) + self.assertEqual(env.sign_key, mock.sentinel.default) + ui.notify.assert_called_once() + + @mock.patch('alot.commands.envelope.settings.account_matching_address', + mock.Mock(side_effect=NoMatchingAccount)) + def test_apply_no_keyid_nomatchingaccount(self): + """If there is a nokeyid and no account can be found to match the From, + then the envelope should not be marked to sign. + """ + env, ui = self._make_ui_mock() + # The actual keyid doesn't matter, since it'll be mocked anyway + cmd = envelope.SignCommand(action='sign', keyid=None) + cmd.apply(ui) + + self.assertFalse(env.sign) + self.assertEqual(env.sign_key, mock.sentinel.default) + ui.notify.assert_called_once() + + def test_apply_no_keyid_no_gpg_key(self): + """If there is a nokeyid and the account has no gpg key then the + signing key and to sign should be set to false and default. + """ + env, ui = self._make_ui_mock() + env.account = mock.Mock(gpg_key=None) + + cmd = envelope.SignCommand(action='sign', keyid=None) + cmd.apply(ui) + + self.assertFalse(env.sign) + self.assertEqual(env.sign_key, mock.sentinel.default) + ui.notify.assert_called_once() + + def test_apply_no_keyid_default(self): + """If there is no keyid and the account has a gpg key, then that should + be used. + """ + env, ui = self._make_ui_mock() + env.account = mock.Mock(gpg_key='sentinel') + + cmd = envelope.SignCommand(action='sign', keyid=None) + cmd.apply(ui) + + self.assertTrue(env.sign) + self.assertEqual(env.sign_key, 'sentinel') + + @mock.patch('alot.commands.envelope.crypto.get_key', + mock.Mock(return_value=mock.sentinel.keyid)) + def test_apply_no_sign(self): + """If signing with a valid keyid and valid key then set sign and + sign_key. + """ + env, ui = self._make_ui_mock() + # The actual keyid doesn't matter, since it'll be mocked anyway + cmd = envelope.SignCommand(action='sign', keyid=['a']) + cmd.apply(ui) + + self.assertTrue(env.sign) + self.assertEqual(env.sign_key, mock.sentinel.keyid) + + @mock.patch('alot.commands.envelope.crypto.get_key', + mock.Mock(return_value=mock.sentinel.keyid)) + def test_apply_unsign(self): + """Test that settingun sign sets the sign to False if all other + conditions allow for it. + """ + env, ui = self._make_ui_mock() + env.sign = True + env.sign_key = mock.sentinel + # The actual keyid doesn't matter, since it'll be mocked anyway + cmd = envelope.SignCommand(action='unsign', keyid=['a']) + cmd.apply(ui) + + self.assertFalse(env.sign) + self.assertIs(env.sign_key, None) + + @mock.patch('alot.commands.envelope.crypto.get_key', + mock.Mock(return_value=mock.sentinel.keyid)) + def test_apply_togglesign(self): + """Test that toggling changes the sign and sign_key as approriate if + other condtiions allow for it + """ + env, ui = self._make_ui_mock() + env.sign = True + env.sign_key = mock.sentinel.keyid + + # The actual keyid doesn't matter, since it'll be mocked anyway + # Test that togling from true to false works + cmd = envelope.SignCommand(action='toggle', keyid=['a']) + cmd.apply(ui) + self.assertFalse(env.sign) + self.assertIs(env.sign_key, None) + + # Test that toggling back to True works + cmd.apply(ui) + self.assertTrue(env.sign) + self.assertIs(env.sign_key, mock.sentinel.keyid) + + def _make_local_settings(self): + config = textwrap.dedent("""\ + [accounts] + [[default]] + realname = foo + address = foo@example.com + sendmail_command = /bin/true + """) + + # Allow settings.reload to work by not deleting the file until the end + with tempfile.NamedTemporaryFile(mode='w+', delete=False) as f: + f.write(config) + self.addCleanup(os.unlink, f.name) + + # Set the gpg_key separately to avoid validation failures + manager = SettingsManager() + manager.read_config(f.name) + manager.get_accounts()[0].gpg_key = mock.sentinel.gpg_key + return manager + + def test_apply_from_email_only(self): + """Test that a key can be derived using a 'From' header that contains + only an email. + + If the from header is in the form "foo@example.com" and a key exists it + should be used. + """ + manager = self._make_local_settings() + env, ui = self._make_ui_mock() + env.headers = {'From': ['foo@example.com']} + + cmd = envelope.SignCommand(action='sign') + with mock.patch('alot.commands.envelope.settings', manager): + cmd.apply(ui) + + self.assertTrue(env.sign) + self.assertIs(env.sign_key, mock.sentinel.gpg_key) + + def test_apply_from_user_and_email(self): + """This tests that a gpg key can be derived using a 'From' header that + contains a realname-email combo. + + If the header is in the form "Foo ", a key should be + derived. + + See issue #1113 + """ + manager = self._make_local_settings() + env, ui = self._make_ui_mock() + + cmd = envelope.SignCommand(action='sign') + with mock.patch('alot.commands.envelope.settings', manager): + cmd.apply(ui) + + self.assertTrue(env.sign) + self.assertIs(env.sign_key, mock.sentinel.gpg_key) + + +class TestSendCommand(unittest.TestCase): + + """Tests for the SendCommand class.""" + + mail = textwrap.dedent("""\ + From: foo@example.com + To: bar@example.com + Subject: FooBar + + Foo Bar Baz + """) + + class MockedAccount(Account): + + def __init__(self): + super().__init__('foo@example.com') + + async def send_mail(self, mail): + pass + + @utilities.async_test + async def test_account_matching_address_with_str(self): + cmd = envelope.SendCommand(mail=self.mail) + account = mock.Mock(wraps=self.MockedAccount()) + with mock.patch( + 'alot.commands.envelope.settings.account_matching_address', + mock.Mock(return_value=account)) as account_matching_address: + await cmd.apply(mock.Mock()) + account_matching_address.assert_called_once_with('foo@example.com', + return_default=True) + # check that the apply did run through till the end. + account.send_mail.assert_called_once_with(self.mail) + + @utilities.async_test + async def test_account_matching_address_with_email_message(self): + mail = email.message_from_string(self.mail) + cmd = envelope.SendCommand(mail=mail) + account = mock.Mock(wraps=self.MockedAccount()) + with mock.patch( + 'alot.commands.envelope.settings.account_matching_address', + mock.Mock(return_value=account)) as account_matching_address: + await cmd.apply(mock.Mock()) + account_matching_address.assert_called_once_with('foo@example.com', + return_default=True) + # check that the apply did run through till the end. + account.send_mail.assert_called_once_with(mail) diff --git a/tests/commands/test_global.py b/tests/commands/test_global.py new file mode 100644 index 00000000..9b026e6a --- /dev/null +++ b/tests/commands/test_global.py @@ -0,0 +1,246 @@ +# encoding=utf-8 +# Copyright © 2017-2018 Dylan Baker +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Tests for global commands.""" + +import logging +import os +import tempfile +import unittest + +import mock + +from alot.commands import globals as g_commands + +from .. import utilities + + +class Stop(Exception): + """exception for stopping testing of giant unmanagable functions.""" + pass + + +class TestComposeCommand(unittest.TestCase): + + """Tests for the compose command.""" + + @staticmethod + def _make_envelope_mock(): + envelope = mock.Mock() + envelope.headers = {'From': 'foo '} + envelope.get = envelope.headers.get + envelope.sign_key = None + envelope.sign = False + return envelope + + @staticmethod + def _make_account_mock( + sign_by_default=True, gpg_key=mock.sentinel.gpg_key): + account = mock.Mock() + account.sign_by_default = sign_by_default + account.gpg_key = gpg_key + account.signature = None + return account + + @utilities.async_test + async def test_apply_sign_by_default_okay(self): + envelope = self._make_envelope_mock() + envelope.account = self._make_account_mock() + cmd = g_commands.ComposeCommand(envelope=envelope) + + with mock.patch('alot.commands.globals.settings.get_addressbooks', + mock.Mock(side_effect=Stop)): + try: + await cmd.apply(mock.Mock()) + except Stop: + pass + + self.assertTrue(envelope.sign) + self.assertIs(envelope.sign_key, mock.sentinel.gpg_key) + + @utilities.async_test + async def test_apply_sign_by_default_false_doesnt_set_key(self): + envelope = self._make_envelope_mock() + envelope.account = self._make_account_mock(sign_by_default=False) + cmd = g_commands.ComposeCommand(envelope=envelope) + + with mock.patch('alot.commands.globals.settings.get_addressbooks', + mock.Mock(side_effect=Stop)): + try: + await cmd.apply(mock.Mock()) + except Stop: + pass + + self.assertFalse(envelope.sign) + self.assertIs(envelope.sign_key, None) + + @utilities.async_test + async def test_apply_sign_by_default_but_no_key(self): + envelope = self._make_envelope_mock() + envelope.account = self._make_account_mock(gpg_key=None) + cmd = g_commands.ComposeCommand(envelope=envelope) + + with mock.patch('alot.commands.globals.settings.get_addressbooks', + mock.Mock(side_effect=Stop)): + try: + await cmd.apply(mock.Mock()) + except Stop: + pass + + self.assertFalse(envelope.sign) + self.assertIs(envelope.sign_key, None) + + @utilities.async_test + async def test_decode_template_on_loading(self): + subject = u'This is a täßϑ subject.' + to = u'recipient@mail.com' + _from = u'foo.bar@mail.fr' + body = u'Body\n地初店会継思識棋御招告外児山望掲領環。\n€mail body €nd.' + with tempfile.NamedTemporaryFile('wb', delete=False) as f: + txt = u'Subject: {}\nTo: {}\nFrom: {}\n{}'.format(subject, to, + _from, body) + f.write(txt.encode('utf-8')) + self.addCleanup(os.unlink, f.name) + + cmd = g_commands.ComposeCommand(template=f.name) + + # Crutch to exit the giant `apply` method early. + with mock.patch( + 'alot.commands.globals.settings.get_accounts', + mock.Mock(side_effect=Stop)): + try: + await cmd.apply(mock.Mock()) + except Stop: + pass + + self.assertEqual({'To': [to], + 'From': [_from], + 'Subject': [subject]}, cmd.envelope.headers) + self.assertEqual(body, cmd.envelope.body) + + @utilities.async_test + async def test_single_account_no_from(self): + # issue #1277 + envelope = self._make_envelope_mock() + del envelope.headers['From'] + envelope.account = self._make_account_mock() + envelope.account.realname = "foo" + envelope.account.address = 1 # maybe this should be a real Address? + cmd = g_commands.ComposeCommand(envelope=envelope) + + with mock.patch('alot.commands.globals.settings.get_addressbooks', + mock.Mock(side_effect=Stop)): + try: + await cmd.apply(mock.Mock()) + except Stop: + pass + + +class TestExternalCommand(unittest.TestCase): + + @utilities.async_test + async def test_no_spawn_no_stdin_success(self): + ui = utilities.make_ui() + cmd = g_commands.ExternalCommand(u'true', refocus=False) + await cmd.apply(ui) + ui.notify.assert_not_called() + + @utilities.async_test + async def test_no_spawn_stdin_success(self): + ui = utilities.make_ui() + cmd = g_commands.ExternalCommand(u"awk '{ exit $0 }'", stdin=u'0', + refocus=False) + await cmd.apply(ui) + ui.notify.assert_not_called() + + @utilities.async_test + async def test_no_spawn_no_stdin_attached(self): + ui = utilities.make_ui() + cmd = g_commands.ExternalCommand(u'test -t 0', refocus=False) + await cmd.apply(ui) + ui.notify.assert_not_called() + + @utilities.async_test + async def test_no_spawn_stdin_attached(self): + ui = utilities.make_ui() + cmd = g_commands.ExternalCommand( + u"test -t 0", stdin=u'0', refocus=False) + await cmd.apply(ui) + ui.notify.assert_called_once_with('', priority='error') + + @utilities.async_test + async def test_no_spawn_failure(self): + ui = utilities.make_ui() + cmd = g_commands.ExternalCommand(u'false', refocus=False) + await cmd.apply(ui) + ui.notify.assert_called_once_with('', priority='error') + + @utilities.async_test + @mock.patch( + 'alot.commands.globals.settings.get', mock.Mock(return_value='')) + @mock.patch.dict(os.environ, {'DISPLAY': ':0'}) + async def test_spawn_no_stdin_success(self): + ui = utilities.make_ui() + cmd = g_commands.ExternalCommand(u'true', refocus=False, spawn=True) + await cmd.apply(ui) + ui.notify.assert_not_called() + + @utilities.async_test + @mock.patch( + 'alot.commands.globals.settings.get', mock.Mock(return_value='')) + @mock.patch.dict(os.environ, {'DISPLAY': ':0'}) + async def test_spawn_stdin_success(self): + ui = utilities.make_ui() + cmd = g_commands.ExternalCommand( + u"awk '{ exit $0 }'", + stdin=u'0', refocus=False, spawn=True) + await cmd.apply(ui) + ui.notify.assert_not_called() + + @utilities.async_test + @mock.patch( + 'alot.commands.globals.settings.get', mock.Mock(return_value='')) + @mock.patch.dict(os.environ, {'DISPLAY': ':0'}) + async def test_spawn_failure(self): + ui = utilities.make_ui() + cmd = g_commands.ExternalCommand(u'false', refocus=False, spawn=True) + await cmd.apply(ui) + ui.notify.assert_called_once_with('', priority='error') + + +class TestCallCommand(unittest.TestCase): + + @utilities.async_test + async def test_synchronous_call(self): + ui = mock.Mock() + cmd = g_commands.CallCommand('ui()') + await cmd.apply(ui) + ui.assert_called_once() + + @utilities.async_test + async def test_async_call(self): + async def func(obj): + obj() + + ui = mock.Mock() + hooks = mock.Mock() + hooks.ui = None + hooks.func = func + + with mock.patch('alot.commands.globals.settings.hooks', hooks): + cmd = g_commands.CallCommand('hooks.func(ui)') + await cmd.apply(ui) + ui.assert_called_once() diff --git a/tests/commands/test_init.py b/tests/commands/test_init.py new file mode 100644 index 00000000..a2b358ff --- /dev/null +++ b/tests/commands/test_init.py @@ -0,0 +1,50 @@ +# encoding=utf-8 + +"""Test suite for alot.commands.__init__ module.""" + +import argparse +import unittest + +import mock + +from alot import commands +from alot.commands import thread + +# Good descriptive test names often don't fit PEP8, which is meant to cover +# functions meant to be called by humans. +# pylint: disable=invalid-name + +# These are tests, don't worry about names like "foo" and "bar" +# pylint: disable=blacklisted-name + + +class TestLookupCommand(unittest.TestCase): + + def test_look_up_save_attachment_command_in_thread_mode(self): + cmd, parser, kwargs = commands.lookup_command('save', 'thread') + # TODO do some more tests with these return values + self.assertEqual(cmd, thread.SaveAttachmentCommand) + self.assertIsInstance(parser, argparse.ArgumentParser) + self.assertDictEqual(kwargs, {}) + + +class TestCommandFactory(unittest.TestCase): + + def test_create_save_attachment_command_with_arguments(self): + cmd = commands.commandfactory('save --all /foo', mode='thread') + self.assertIsInstance(cmd, thread.SaveAttachmentCommand) + self.assertTrue(cmd.all) + self.assertEqual(cmd.path, u'/foo') + + +class TestRegisterCommand(unittest.TestCase): + """Tests for the registerCommand class.""" + + def test_registered(self): + """using registerCommand adds to the COMMANDS dict.""" + with mock.patch('alot.commands.COMMANDS', {'foo': {}}): + @commands.registerCommand('foo', 'test') + def foo(): # pylint: disable=unused-variable + pass + + self.assertIn('test', commands.COMMANDS['foo']) diff --git a/tests/commands/test_thread.py b/tests/commands/test_thread.py new file mode 100644 index 00000000..634c35e8 --- /dev/null +++ b/tests/commands/test_thread.py @@ -0,0 +1,230 @@ +# encoding=utf-8 +# Copyright (C) 2017 Lucas Hoffmann +# This file is released under the GNU GPL, version 3 or a later revision. +# For further details see the COPYING file + +"""Test suite for alot.commands.thread module.""" +import email +import unittest + +import mock + +from alot.commands import thread +from alot.account import Account + +# Good descriptive test names often don't fit PEP8, which is meant to cover +# functions meant to be called by humans. +# pylint: disable=invalid-name + +# These are tests, don't worry about names like "foo" and "bar" +# pylint: disable=blacklisted-name + + +class Test_ensure_unique_address(unittest.TestCase): + + foo = 'foo ' + foo2 = 'foo the fanzy ' + bar = 'bar ' + baz = 'baz ' + + def test_unique_lists_are_unchanged(self): + expected = sorted([self.foo, self.bar]) + actual = thread.ReplyCommand.ensure_unique_address(expected) + self.assertListEqual(actual, expected) + + def test_equal_entries_are_detected(self): + actual = thread.ReplyCommand.ensure_unique_address( + [self.foo, self.bar, self.foo]) + expected = sorted([self.foo, self.bar]) + self.assertListEqual(actual, expected) + + def test_same_address_with_different_name_is_detected(self): + actual = thread.ReplyCommand.ensure_unique_address( + [self.foo, self.foo2]) + expected = [self.foo2] + self.assertListEqual(actual, expected) + + +class _AccountTestClass(Account): + """Implements stubs for ABC methods.""" + + def send_mail(self, mail): + pass + + +class TestClearMyAddress(unittest.TestCase): + + me1 = u'me@example.com' + me2 = u'ME@example.com' + me3 = u'me+label@example.com' + me4 = u'ME+label@example.com' + me_regex = r'me\+.*@example.com' + me_named = u'alot team ' + you = u'you@example.com' + named = u'somebody you know ' + imposter = u'alot team ' + mine = _AccountTestClass( + address=me1, aliases=[], alias_regexp=me_regex, case_sensitive_username=True) + + + def test_empty_input_returns_empty_list(self): + self.assertListEqual( + thread.ReplyCommand.clear_my_address(self.mine, []), []) + + def test_only_my_emails_result_in_empty_list(self): + expected = [] + actual = thread.ReplyCommand.clear_my_address( + self.mine, [self.me1, self.me3, self.me_named]) + self.assertListEqual(actual, expected) + + def test_other_emails_are_untouched(self): + input_ = [self.you, self.me1, self.me_named, self.named] + expected = [self.you, self.named] + actual = thread.ReplyCommand.clear_my_address(self.mine, input_) + self.assertListEqual(actual, expected) + + def test_case_matters(self): + input_ = [self.me1, self.me2, self.me3, self.me4] + expected = [self.me2, self.me4] + actual = thread.ReplyCommand.clear_my_address(self.mine, input_) + self.assertListEqual(actual, expected) + + def test_same_address_with_different_real_name_is_removed(self): + input_ = [self.me_named, self.you] + expected = [self.you] + actual = thread.ReplyCommand.clear_my_address(self.mine, input_) + self.assertListEqual(actual, expected) + + +class _AccountTestClass(Account): + """Implements stubs for ABC methods.""" + + def send_mail(self, mail): + pass + + +class TestDetermineSender(unittest.TestCase): + + header_priority = ["From", "To", "Cc", "Envelope-To", "X-Envelope-To", + "Delivered-To"] + mailstring = '\n'.join([ + "From: from@example.com", + "To: to@example.com", + "Cc: cc@example.com", + "Envelope-To: envelope-to@example.com", + "X-Envelope-To: x-envelope-to@example.com", + "Delivered-To: delivered-to@example.com", + "Subject: Alot test", + "\n", + "Some content", + ]) + mail = email.message_from_string(mailstring) + + def _test(self, accounts=(), expected=(), mail=None, header_priority=None, + force_realname=False, force_address=False): + """This method collects most of the steps that need to be done for most + tests. Especially a closure to mock settings.get and a mock for + settings.get_accounts are set up.""" + mail = self.mail if mail is None else mail + header_priority = self.header_priority if header_priority is None \ + else header_priority + + def settings_get(arg): + """Mock function for setting.get()""" + if arg == "reply_account_header_priority": + return header_priority + elif arg.endswith('_force_realname'): + return force_realname + elif arg.endswith('_force_address'): + return force_address + + with mock.patch('alot.commands.thread.settings.get_accounts', + mock.Mock(return_value=accounts)): + with mock.patch('alot.commands.thread.settings.get', settings_get): + actual = thread.determine_sender(mail) + self.assertTupleEqual(actual, expected) + + def test_assert_that_some_accounts_are_defined(self): + with mock.patch('alot.commands.thread.settings.get_accounts', + mock.Mock(return_value=[])) as cm1: + with self.assertRaises(AssertionError) as cm2: + thread.determine_sender(None) + expected = ('no accounts set!',) + cm1.assert_called_once_with() + self.assertTupleEqual(cm2.exception.args, expected) + + def test_default_account_is_used_if_no_match_is_found(self): + account1 = _AccountTestClass(address=u'foo@example.com') + account2 = _AccountTestClass(address=u'bar@example.com') + expected = (u'foo@example.com', account1) + self._test(accounts=[account1, account2], expected=expected) + + def test_matching_address_and_account_are_returned(self): + account1 = _AccountTestClass(address=u'foo@example.com') + account2 = _AccountTestClass(address=u'to@example.com') + account3 = _AccountTestClass(address=u'bar@example.com') + expected = (u'to@example.com', account2) + self._test(accounts=[account1, account2, account3], expected=expected) + + def test_force_realname_has_real_name_in_returned_address_if_defined(self): + account1 = _AccountTestClass(address=u'foo@example.com') + account2 = _AccountTestClass(address=u'to@example.com', realname='Bar') + account3 = _AccountTestClass(address=u'baz@example.com') + expected = (u'Bar ', account2) + self._test(accounts=[account1, account2, account3], expected=expected, + force_realname=True) + + def test_doesnt_fail_with_force_realname_if_real_name_not_defined(self): + account1 = _AccountTestClass(address=u'foo@example.com') + account2 = _AccountTestClass(address=u'to@example.com') + account3 = _AccountTestClass(address=u'bar@example.com') + expected = (u'to@example.com', account2) + self._test(accounts=[account1, account2, account3], expected=expected, + force_realname=True) + + def test_with_force_address_main_address_is_always_used(self): + # In python 3.4 this and the next test could be written as subtests. + account1 = _AccountTestClass(address=u'foo@example.com') + account2 = _AccountTestClass(address=u'bar@example.com', + aliases=[u'to@example.com']) + account3 = _AccountTestClass(address=u'bar@example.com') + expected = (u'bar@example.com', account2) + self._test(accounts=[account1, account2, account3], expected=expected, + force_address=True) + + def test_without_force_address_matching_address_is_used(self): + # In python 3.4 this and the previous test could be written as + # subtests. + account1 = _AccountTestClass(address=u'foo@example.com') + account2 = _AccountTestClass(address=u'bar@example.com', + aliases=[u'to@example.com']) + account3 = _AccountTestClass(address=u'baz@example.com') + expected = (u'to@example.com', account2) + self._test(accounts=[account1, account2, account3], expected=expected, + force_address=False) + + def test_uses_to_header_if_present(self): + account1 = _AccountTestClass(address=u'foo@example.com') + account2 = _AccountTestClass(address=u'to@example.com') + account3 = _AccountTestClass(address=u'bar@example.com') + expected = (u'to@example.com', account2) + self._test(accounts=[account1, account2, account3], expected=expected) + + def test_header_order_is_more_important_than_accounts_order(self): + account1 = _AccountTestClass(address=u'cc@example.com') + account2 = _AccountTestClass(address=u'to@example.com') + account3 = _AccountTestClass(address=u'bcc@example.com') + expected = (u'to@example.com', account2) + self._test(accounts=[account1, account2, account3], expected=expected) + + def test_accounts_can_be_found_by_alias_regex_setting(self): + account1 = _AccountTestClass(address=u'foo@example.com') + account2 = _AccountTestClass(address=u'to@example.com', + alias_regexp=r'to\+.*@example.com') + account3 = _AccountTestClass(address=u'bar@example.com') + mailstring = self.mailstring.replace(u'to@example.com', + u'to+some_tag@example.com') + mail = email.message_from_string(mailstring) + expected = (u'to+some_tag@example.com', account2) + self._test(accounts=[account1, account2, account3], expected=expected, + mail=mail) diff --git a/tests/commands/thread_test.py b/tests/commands/thread_test.py deleted file mode 100644 index 634c35e8..00000000 --- a/tests/commands/thread_test.py +++ /dev/null @@ -1,230 +0,0 @@ -# encoding=utf-8 -# Copyright (C) 2017 Lucas Hoffmann -# This file is released under the GNU GPL, version 3 or a later revision. -# For further details see the COPYING file - -"""Test suite for alot.commands.thread module.""" -import email -import unittest - -import mock - -from alot.commands import thread -from alot.account import Account - -# Good descriptive test names often don't fit PEP8, which is meant to cover -# functions meant to be called by humans. -# pylint: disable=invalid-name - -# These are tests, don't worry about names like "foo" and "bar" -# pylint: disable=blacklisted-name - - -class Test_ensure_unique_address(unittest.TestCase): - - foo = 'foo ' - foo2 = 'foo the fanzy ' - bar = 'bar ' - baz = 'baz ' - - def test_unique_lists_are_unchanged(self): - expected = sorted([self.foo, self.bar]) - actual = thread.ReplyCommand.ensure_unique_address(expected) - self.assertListEqual(actual, expected) - - def test_equal_entries_are_detected(self): - actual = thread.ReplyCommand.ensure_unique_address( - [self.foo, self.bar, self.foo]) - expected = sorted([self.foo, self.bar]) - self.assertListEqual(actual, expected) - - def test_same_address_with_different_name_is_detected(self): - actual = thread.ReplyCommand.ensure_unique_address( - [self.foo, self.foo2]) - expected = [self.foo2] - self.assertListEqual(actual, expected) - - -class _AccountTestClass(Account): - """Implements stubs for ABC methods.""" - - def send_mail(self, mail): - pass - - -class TestClearMyAddress(unittest.TestCase): - - me1 = u'me@example.com' - me2 = u'ME@example.com' - me3 = u'me+label@example.com' - me4 = u'ME+label@example.com' - me_regex = r'me\+.*@example.com' - me_named = u'alot team ' - you = u'you@example.com' - named = u'somebody you know ' - imposter = u'alot team ' - mine = _AccountTestClass( - address=me1, aliases=[], alias_regexp=me_regex, case_sensitive_username=True) - - - def test_empty_input_returns_empty_list(self): - self.assertListEqual( - thread.ReplyCommand.clear_my_address(self.mine, []), []) - - def test_only_my_emails_result_in_empty_list(self): - expected = [] - actual = thread.ReplyCommand.clear_my_address( - self.mine, [self.me1, self.me3, self.me_named]) - self.assertListEqual(actual, expected) - - def test_other_emails_are_untouched(self): - input_ = [self.you, self.me1, self.me_named, self.named] - expected = [self.you, self.named] - actual = thread.ReplyCommand.clear_my_address(self.mine, input_) - self.assertListEqual(actual, expected) - - def test_case_matters(self): - input_ = [self.me1, self.me2, self.me3, self.me4] - expected = [self.me2, self.me4] - actual = thread.ReplyCommand.clear_my_address(self.mine, input_) - self.assertListEqual(actual, expected) - - def test_same_address_with_different_real_name_is_removed(self): - input_ = [self.me_named, self.you] - expected = [self.you] - actual = thread.ReplyCommand.clear_my_address(self.mine, input_) - self.assertListEqual(actual, expected) - - -class _AccountTestClass(Account): - """Implements stubs for ABC methods.""" - - def send_mail(self, mail): - pass - - -class TestDetermineSender(unittest.TestCase): - - header_priority = ["From", "To", "Cc", "Envelope-To", "X-Envelope-To", - "Delivered-To"] - mailstring = '\n'.join([ - "From: from@example.com", - "To: to@example.com", - "Cc: cc@example.com", - "Envelope-To: envelope-to@example.com", - "X-Envelope-To: x-envelope-to@example.com", - "Delivered-To: delivered-to@example.com", - "Subject: Alot test", - "\n", - "Some content", - ]) - mail = email.message_from_string(mailstring) - - def _test(self, accounts=(), expected=(), mail=None, header_priority=None, - force_realname=False, force_address=False): - """This method collects most of the steps that need to be done for most - tests. Especially a closure to mock settings.get and a mock for - settings.get_accounts are set up.""" - mail = self.mail if mail is None else mail - header_priority = self.header_priority if header_priority is None \ - else header_priority - - def settings_get(arg): - """Mock function for setting.get()""" - if arg == "reply_account_header_priority": - return header_priority - elif arg.endswith('_force_realname'): - return force_realname - elif arg.endswith('_force_address'): - return force_address - - with mock.patch('alot.commands.thread.settings.get_accounts', - mock.Mock(return_value=accounts)): - with mock.patch('alot.commands.thread.settings.get', settings_get): - actual = thread.determine_sender(mail) - self.assertTupleEqual(actual, expected) - - def test_assert_that_some_accounts_are_defined(self): - with mock.patch('alot.commands.thread.settings.get_accounts', - mock.Mock(return_value=[])) as cm1: - with self.assertRaises(AssertionError) as cm2: - thread.determine_sender(None) - expected = ('no accounts set!',) - cm1.assert_called_once_with() - self.assertTupleEqual(cm2.exception.args, expected) - - def test_default_account_is_used_if_no_match_is_found(self): - account1 = _AccountTestClass(address=u'foo@example.com') - account2 = _AccountTestClass(address=u'bar@example.com') - expected = (u'foo@example.com', account1) - self._test(accounts=[account1, account2], expected=expected) - - def test_matching_address_and_account_are_returned(self): - account1 = _AccountTestClass(address=u'foo@example.com') - account2 = _AccountTestClass(address=u'to@example.com') - account3 = _AccountTestClass(address=u'bar@example.com') - expected = (u'to@example.com', account2) - self._test(accounts=[account1, account2, account3], expected=expected) - - def test_force_realname_has_real_name_in_returned_address_if_defined(self): - account1 = _AccountTestClass(address=u'foo@example.com') - account2 = _AccountTestClass(address=u'to@example.com', realname='Bar') - account3 = _AccountTestClass(address=u'baz@example.com') - expected = (u'Bar ', account2) - self._test(accounts=[account1, account2, account3], expected=expected, - force_realname=True) - - def test_doesnt_fail_with_force_realname_if_real_name_not_defined(self): - account1 = _AccountTestClass(address=u'foo@example.com') - account2 = _AccountTestClass(address=u'to@example.com') - account3 = _AccountTestClass(address=u'bar@example.com') - expected = (u'to@example.com', account2) - self._test(accounts=[account1, account2, account3], expected=expected, - force_realname=True) - - def test_with_force_address_main_address_is_always_used(self): - # In python 3.4 this and the next test could be written as subtests. - account1 = _AccountTestClass(address=u'foo@example.com') - account2 = _AccountTestClass(address=u'bar@example.com', - aliases=[u'to@example.com']) - account3 = _AccountTestClass(address=u'bar@example.com') - expected = (u'bar@example.com', account2) - self._test(accounts=[account1, account2, account3], expected=expected, - force_address=True) - - def test_without_force_address_matching_address_is_used(self): - # In python 3.4 this and the previous test could be written as - # subtests. - account1 = _AccountTestClass(address=u'foo@example.com') - account2 = _AccountTestClass(address=u'bar@example.com', - aliases=[u'to@example.com']) - account3 = _AccountTestClass(address=u'baz@example.com') - expected = (u'to@example.com', account2) - self._test(accounts=[account1, account2, account3], expected=expected, - force_address=False) - - def test_uses_to_header_if_present(self): - account1 = _AccountTestClass(address=u'foo@example.com') - account2 = _AccountTestClass(address=u'to@example.com') - account3 = _AccountTestClass(address=u'bar@example.com') - expected = (u'to@example.com', account2) - self._test(accounts=[account1, account2, account3], expected=expected) - - def test_header_order_is_more_important_than_accounts_order(self): - account1 = _AccountTestClass(address=u'cc@example.com') - account2 = _AccountTestClass(address=u'to@example.com') - account3 = _AccountTestClass(address=u'bcc@example.com') - expected = (u'to@example.com', account2) - self._test(accounts=[account1, account2, account3], expected=expected) - - def test_accounts_can_be_found_by_alias_regex_setting(self): - account1 = _AccountTestClass(address=u'foo@example.com') - account2 = _AccountTestClass(address=u'to@example.com', - alias_regexp=r'to\+.*@example.com') - account3 = _AccountTestClass(address=u'bar@example.com') - mailstring = self.mailstring.replace(u'to@example.com', - u'to+some_tag@example.com') - mail = email.message_from_string(mailstring) - expected = (u'to+some_tag@example.com', account2) - self._test(accounts=[account1, account2, account3], expected=expected, - mail=mail) -- cgit v1.2.3