"""Tests for certbot_postfix.util."""

import subprocess
import unittest

import mock

from certbot import errors


class PostfixUtilBaseTest(unittest.TestCase):
    """Tests for certbot_postfix.util.PostfixUtilBase."""

    @classmethod
    def _create_object(cls, *args, **kwargs):
        from certbot_postfix.util import PostfixUtilBase
        return PostfixUtilBase(*args, **kwargs)

    @mock.patch('certbot_postfix.util.verify_exe_exists')
    def test_no_exe(self, mock_verify):
        expected_error = errors.NoInstallationError
        mock_verify.side_effect = expected_error
        self.assertRaises(expected_error, self._create_object, 'nonexistent')

    def test_object_creation(self):
        with mock.patch('certbot_postfix.util.verify_exe_exists'):
            self._create_object('existent')

    @mock.patch('certbot_postfix.util.check_all_output')
    def test_call_extends_args(self, mock_output):
        # pylint: disable=protected-access
        with mock.patch('certbot_postfix.util.verify_exe_exists'):
            mock_output.return_value = 'expected'
            postfix = self._create_object('executable')
            postfix._call(['many', 'extra', 'args'])
            mock_output.assert_called_with(['executable', 'many', 'extra', 'args'])
            postfix._call()
            mock_output.assert_called_with(['executable'])

    def test_create_with_config(self):
        # pylint: disable=protected-access
        with mock.patch('certbot_postfix.util.verify_exe_exists'):
            postfix = self._create_object('exec', 'config_dir')
            self.assertEqual(postfix._base_command, ['exec', '-c', 'config_dir'])

class PostfixUtilTest(unittest.TestCase):
    def setUp(self):
        # pylint: disable=protected-access
        from certbot_postfix.util import PostfixUtil
        with mock.patch('certbot_postfix.util.verify_exe_exists'):
            self.postfix = PostfixUtil()
            self.postfix._call = mock.Mock()
            self.mock_call = self.postfix._call

    def test_test(self):
        self.postfix.test()
        self.mock_call.assert_called_with(['check'])

    def test_test_raises_error_when_check_fails(self):
        self.mock_call.side_effect = [subprocess.CalledProcessError(1, "")]
        self.assertRaises(errors.MisconfigurationError, self.postfix.test)
        self.mock_call.assert_called_with(['check'])

    def test_restart_while_running(self):
        self.mock_call.side_effect = [subprocess.CalledProcessError(1, ""), None]
        self.postfix.restart()
        self.mock_call.assert_called_with(['start'])

    def test_restart_while_not_running(self):
        self.postfix.restart()
        self.mock_call.assert_called_with(['reload'])

    def test_restart_raises_error_when_reload_fails(self):
        self.mock_call.side_effect = [None, subprocess.CalledProcessError(1, "")]
        self.assertRaises(errors.PluginError, self.postfix.restart)
        self.mock_call.assert_called_with(['reload'])

    def test_restart_raises_error_when_start_fails(self):
        self.mock_call.side_effect = [
             subprocess.CalledProcessError(1, ""),
             subprocess.CalledProcessError(1, "")]
        self.assertRaises(errors.PluginError, self.postfix.restart)
        self.mock_call.assert_called_with(['start'])

class CheckAllOutputTest(unittest.TestCase):
    """Tests for certbot_postfix.util.check_all_output."""

    @classmethod
    def _call(cls, *args, **kwargs):
        from certbot_postfix.util import check_all_output
        return check_all_output(*args, **kwargs)

    @mock.patch('certbot_postfix.util.logger')
    @mock.patch('certbot_postfix.util.subprocess.Popen')
    def test_command_error(self, mock_popen, mock_logger):
        command = 'foo'
        retcode = 42
        output = 'bar'
        err = 'baz'

        mock_popen().communicate.return_value = (output, err)
        mock_popen().poll.return_value = 42

        self.assertRaises(subprocess.CalledProcessError, self._call, command)
        log_args = mock_logger.debug.call_args[0]
        for value in (command, retcode, output, err,):
            self.assertTrue(value in log_args)

    @mock.patch('certbot_postfix.util.subprocess.Popen')
    def test_success(self, mock_popen):
        command = 'foo'
        expected = ('bar', '')
        mock_popen().communicate.return_value = expected
        mock_popen().poll.return_value = 0

        self.assertEqual(self._call(command), expected)

    def test_stdout_error(self):
        self.assertRaises(ValueError, self._call, stdout=None)

    def test_stderr_error(self):
        self.assertRaises(ValueError, self._call, stderr=None)

    def test_universal_newlines_error(self):
        self.assertRaises(ValueError, self._call, universal_newlines=False)


class VerifyExeExistsTest(unittest.TestCase):
    """Tests for certbot_postfix.util.verify_exe_exists."""

    @classmethod
    def _call(cls, *args, **kwargs):
        from certbot_postfix.util import verify_exe_exists
        return verify_exe_exists(*args, **kwargs)

    @mock.patch('certbot_postfix.util.certbot_util.exe_exists')
    @mock.patch('certbot_postfix.util.plugins_util.path_surgery')
    def test_failure(self, mock_exe_exists, mock_path_surgery):
        mock_exe_exists.return_value = mock_path_surgery.return_value = False
        self.assertRaises(errors.NoInstallationError, self._call, 'foo')

    @mock.patch('certbot_postfix.util.certbot_util.exe_exists')
    def test_simple_success(self, mock_exe_exists):
        mock_exe_exists.return_value = True
        self._call('foo')

    @mock.patch('certbot_postfix.util.certbot_util.exe_exists')
    @mock.patch('certbot_postfix.util.plugins_util.path_surgery')
    def test_successful_surgery(self, mock_exe_exists, mock_path_surgery):
        mock_exe_exists.return_value = False
        mock_path_surgery.return_value = True
        self._call('foo')

class TestUtils(unittest.TestCase):
    """ Testing random utility functions in util.py
    """
    def test_report_master_overrides(self):
        from certbot_postfix.util import report_master_overrides
        self.assertRaises(errors.PluginError, report_master_overrides, 'name',
                          [('service/type', 'value')])
        # Shouldn't raise error
        report_master_overrides('name', [('service/type', 'value')],
                                acceptable_overrides=('value',))

    def test_no_acceptable_value(self):
        from certbot_postfix.util import is_acceptable_value
        self.assertFalse(is_acceptable_value('name', 'value', None))

    def test_is_acceptable_value(self):
        from certbot_postfix.util import is_acceptable_value
        self.assertTrue(is_acceptable_value('name', 'value', ('value',)))
        self.assertFalse(is_acceptable_value('name', 'bad', ('value',)))

    def test_is_acceptable_tuples(self):
        from certbot_postfix.util import is_acceptable_value
        self.assertTrue(is_acceptable_value('name', 'value', ('value', 'value1')))
        self.assertFalse(is_acceptable_value('name', 'bad', ('value', 'value1')))

    def test_is_acceptable_protocols(self):
        from certbot_postfix.util import is_acceptable_value
        # SSLv2 and SSLv3 are both not supported, unambiguously
        self.assertFalse(is_acceptable_value('tls_mandatory_protocols_lol',
            'SSLv2, SSLv3', None))
        self.assertFalse(is_acceptable_value('tls_protocols_lol',
            'SSLv2, SSLv3', None))
        self.assertFalse(is_acceptable_value('tls_protocols_lol',
            '!SSLv2, !TLSv1', None))
        self.assertFalse(is_acceptable_value('tls_protocols_lol',
            '!SSLv2, SSLv3, !SSLv3, ', None))
        self.assertTrue(is_acceptable_value('tls_protocols_lol',
            '!SSLv2, !SSLv3', None))
        self.assertTrue(is_acceptable_value('tls_protocols_lol',
            '!SSLv3, !TLSv1, !SSLv2', None))
        # TLSv1.2 is supported unambiguously
        self.assertFalse(is_acceptable_value('tls_protocols_lol',
            'TLSv1, TLSv1.1,', None))
        self.assertFalse(is_acceptable_value('tls_protocols_lol',
            'TLSv1.2, !TLSv1.2,', None))
        self.assertTrue(is_acceptable_value('tls_protocols_lol',
            'TLSv1.2, ', None))
        self.assertTrue(is_acceptable_value('tls_protocols_lol',
            'TLSv1, TLSv1.1, TLSv1.2', None))

if __name__ == '__main__': # pragma: no cover
    unittest.main()
