Enhance verb (#5596)

* Add the cli parameters

* Tests and error messages

* Requested fixes

* Only handle SSL vhosts

* Interactive cert-name selection if not defined on CLI

* Address PR comments

* Address review comments

* Added tests and addressed review comments

* Move cert manager tests to correct file

* Add display ops tests

* Use display util constants instead of hardcoded values in tests
This commit is contained in:
Joona Hoikkala
2018-04-18 11:08:30 -07:00
committed by Brad Warren
parent 261d063b10
commit a024aaf59d
14 changed files with 480 additions and 55 deletions
+98
View File
@@ -569,5 +569,103 @@ class MatchAndCheckOverlaps(storage_test.BaseRenewableCertTest):
self.assertRaises(errors.OverlappingMatchFound, self._call, self.config, None, None, None)
class GetCertnameTest(unittest.TestCase):
"""Tests for certbot.cert_manager."""
def setUp(self):
self.get_utility_patch = test_util.patch_get_utility()
self.mock_get_utility = self.get_utility_patch.start()
self.config = mock.MagicMock()
self.config.certname = None
def tearDown(self):
self.get_utility_patch.stop()
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.lineagename_for_filename')
def test_get_certnames(self, mock_name, mock_files):
mock_files.return_value = ['example.com.conf']
mock_name.return_value = 'example.com'
from certbot import cert_manager
prompt = "Which certificate would you"
self.mock_get_utility().menu.return_value = (display_util.OK, 0)
self.assertEquals(
cert_manager.get_certnames(
self.config, "verb", allow_multiple=False), ['example.com'])
self.assertTrue(
prompt in self.mock_get_utility().menu.call_args[0][0])
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.lineagename_for_filename')
def test_get_certnames_custom_prompt(self, mock_name, mock_files):
mock_files.return_value = ['example.com.conf']
mock_name.return_value = 'example.com'
from certbot import cert_manager
prompt = "custom prompt"
self.mock_get_utility().menu.return_value = (display_util.OK, 0)
self.assertEquals(
cert_manager.get_certnames(
self.config, "verb", allow_multiple=False, custom_prompt=prompt),
['example.com'])
self.assertEquals(self.mock_get_utility().menu.call_args[0][0],
prompt)
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.lineagename_for_filename')
def test_get_certnames_user_abort(self, mock_name, mock_files):
mock_files.return_value = ['example.com.conf']
mock_name.return_value = 'example.com'
from certbot import cert_manager
self.mock_get_utility().menu.return_value = (display_util.CANCEL, 0)
self.assertRaises(
errors.Error,
cert_manager.get_certnames,
self.config, "erroring_anyway", allow_multiple=False)
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.lineagename_for_filename')
def test_get_certnames_allow_multiple(self, mock_name, mock_files):
mock_files.return_value = ['example.com.conf']
mock_name.return_value = 'example.com'
from certbot import cert_manager
prompt = "Which certificate(s) would you"
self.mock_get_utility().checklist.return_value = (display_util.OK,
['example.com'])
self.assertEquals(
cert_manager.get_certnames(
self.config, "verb", allow_multiple=True), ['example.com'])
self.assertTrue(
prompt in self.mock_get_utility().checklist.call_args[0][0])
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.lineagename_for_filename')
def test_get_certnames_allow_multiple_custom_prompt(self, mock_name, mock_files):
mock_files.return_value = ['example.com.conf']
mock_name.return_value = 'example.com'
from certbot import cert_manager
prompt = "custom prompt"
self.mock_get_utility().checklist.return_value = (display_util.OK,
['example.com'])
self.assertEquals(
cert_manager.get_certnames(
self.config, "verb", allow_multiple=True, custom_prompt=prompt),
['example.com'])
self.assertEquals(
self.mock_get_utility().checklist.call_args[0][0],
prompt)
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.lineagename_for_filename')
def test_get_certnames_allow_multiple_user_abort(self, mock_name, mock_files):
mock_files.return_value = ['example.com.conf']
mock_name.return_value = 'example.com'
from certbot import cert_manager
self.mock_get_utility().checklist.return_value = (display_util.CANCEL, [])
self.assertRaises(
errors.Error,
cert_manager.get_certnames,
self.config, "erroring_anyway", allow_multiple=True)
if __name__ == "__main__":
unittest.main() # pragma: no cover
+23
View File
@@ -433,6 +433,22 @@ class EnhanceConfigTest(ClientTestCommon):
self.client.installer.enhance.assert_not_called()
mock_enhancements.ask.assert_not_called()
@mock.patch("certbot.client.logger")
def test_already_exists_header(self, mock_log):
self.config.hsts = True
self._test_with_already_existing()
self.assertTrue(mock_log.warning.called)
self.assertEquals(mock_log.warning.call_args[0][1],
'Strict-Transport-Security')
@mock.patch("certbot.client.logger")
def test_already_exists_redirect(self, mock_log):
self.config.redirect = True
self._test_with_already_existing()
self.assertTrue(mock_log.warning.called)
self.assertEquals(mock_log.warning.call_args[0][1],
'redirect')
def test_no_ask_hsts(self):
self.config.hsts = True
self._test_with_all_supported()
@@ -508,6 +524,13 @@ class EnhanceConfigTest(ClientTestCommon):
self.assertEqual(self.client.installer.save.call_count, 1)
self.assertEqual(self.client.installer.restart.call_count, 1)
def _test_with_already_existing(self):
self.client.installer = mock.MagicMock()
self.client.installer.supported_enhancements.return_value = [
"ensure-http-header", "redirect", "staple-ocsp"]
self.client.installer.enhance.side_effect = errors.PluginEnhancementAlreadyPresent()
self.client.enhance_config([self.domain], None)
class RollbackTest(unittest.TestCase):
"""Tests for certbot.client.rollback."""
+48 -2
View File
@@ -207,9 +207,9 @@ class ChooseNamesTest(unittest.TestCase):
self.mock_install = mock.MagicMock()
@classmethod
def _call(cls, installer):
def _call(cls, installer, question=None):
from certbot.display.ops import choose_names
return choose_names(installer)
return choose_names(installer, question)
@mock.patch("certbot.display.ops._choose_names_manually")
def test_no_installer(self, mock_manual):
@@ -281,6 +281,15 @@ class ChooseNamesTest(unittest.TestCase):
self.assertEqual(names, ["example.com"])
self.assertEqual(mock_util().checklist.call_count, 1)
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_filter_namees_override_question(self, mock_util):
self.mock_install.get_all_names.return_value = set(["example.com"])
mock_util().checklist.return_value = (display_util.OK, ["example.com"])
names = self._call(self.mock_install, "Custom")
self.assertEqual(names, ["example.com"])
self.assertEqual(mock_util().checklist.call_count, 1)
self.assertEqual(mock_util().checklist.call_args[0][0], "Custom")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_filter_names_nothing_selected(self, mock_util):
self.mock_install.get_all_names.return_value = set(["example.com"])
@@ -481,5 +490,42 @@ class ValidatorTests(unittest.TestCase):
self.__validator, "msg", default="")
class ChooseValuesTest(unittest.TestCase):
"""Test choose_values."""
@classmethod
def _call(cls, values, question):
from certbot.display.ops import choose_values
return choose_values(values, question)
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_choose_names_success(self, mock_util):
items = ["first", "second", "third"]
mock_util().checklist.return_value = (display_util.OK, [items[2]])
result = self._call(items, None)
self.assertEquals(result, [items[2]])
self.assertTrue(mock_util().checklist.called)
self.assertEquals(mock_util().checklist.call_args[0][0], None)
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_choose_names_success_question(self, mock_util):
items = ["first", "second", "third"]
question = "Which one?"
mock_util().checklist.return_value = (display_util.OK, [items[1]])
result = self._call(items, question)
self.assertEquals(result, [items[1]])
self.assertTrue(mock_util().checklist.called)
self.assertEquals(mock_util().checklist.call_args[0][0], question)
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_choose_names_user_cancel(self, mock_util):
items = ["first", "second", "third"]
question = "Want to cancel?"
mock_util().checklist.return_value = (display_util.CANCEL, [])
result = self._call(items, question)
self.assertEquals(result, [])
self.assertTrue(mock_util().checklist.called)
self.assertEquals(mock_util().checklist.call_args[0][0], question)
if __name__ == "__main__":
unittest.main() # pragma: no cover
+109
View File
@@ -1533,5 +1533,114 @@ class MakeOrVerifyNeededDirs(test_util.ConfigTestCase):
strict=self.config.strict_permissions)
class EnhanceTest(unittest.TestCase):
"""Tests for certbot.main.enhance."""
def setUp(self):
self.get_utility_patch = test_util.patch_get_utility()
self.mock_get_utility = self.get_utility_patch.start()
def tearDown(self):
self.get_utility_patch.stop()
def _call(self, args):
plugins = disco.PluginsRegistry.find_all()
config = configuration.NamespaceConfig(
cli.prepare_and_parse_args(plugins, args))
with mock.patch('certbot.cert_manager.get_certnames') as mock_certs:
mock_certs.return_value = ['example.com']
with mock.patch('certbot.cert_manager.domains_for_certname') as mock_dom:
mock_dom.return_value = ['example.com']
with mock.patch('certbot.main._init_le_client') as mock_init:
mock_client = mock.MagicMock()
mock_client.config = config
mock_init.return_value = mock_client
main.enhance(config, plugins)
return mock_client # returns the client
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
@mock.patch('certbot.cert_manager.lineage_for_certname')
@mock.patch('certbot.main.display_ops.choose_values')
@mock.patch('certbot.main._find_domains_or_certname')
def test_selection_question(self, mock_find, mock_choose, mock_lineage, _rec):
mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent")
mock_choose.return_value = ['example.com']
mock_find.return_value = (None, None)
with mock.patch('certbot.main.plug_sel.pick_installer') as mock_pick:
self._call(['enhance', '--redirect'])
self.assertTrue(mock_pick.called)
# Check that the message includes "enhancements"
self.assertTrue("enhancements" in mock_pick.call_args[0][3])
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
@mock.patch('certbot.cert_manager.lineage_for_certname')
@mock.patch('certbot.main.display_ops.choose_values')
@mock.patch('certbot.main._find_domains_or_certname')
def test_selection_auth_warning(self, mock_find, mock_choose, mock_lineage, _rec):
mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent")
mock_choose.return_value = ["example.com"]
mock_find.return_value = (None, None)
with mock.patch('certbot.main.plug_sel.pick_installer'):
with mock.patch('certbot.main.plug_sel.logger.warning') as mock_log:
mock_client = self._call(['enhance', '-a', 'webroot', '--redirect'])
self.assertTrue(mock_log.called)
self.assertTrue("make sense" in mock_log.call_args[0][0])
self.assertTrue(mock_client.enhance_config.called)
@mock.patch('certbot.cert_manager.lineage_for_certname')
@mock.patch('certbot.main.display_ops.choose_values')
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
def test_enhance_config_call(self, _rec, mock_choose, mock_lineage):
mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent")
mock_choose.return_value = ["example.com"]
with mock.patch('certbot.main.plug_sel.pick_installer'):
mock_client = self._call(['enhance', '--redirect', '--hsts'])
req_enh = ["redirect", "hsts"]
not_req_enh = ["uir"]
self.assertTrue(mock_client.enhance_config.called)
self.assertTrue(
all([getattr(mock_client.config, e) for e in req_enh]))
self.assertFalse(
any([getattr(mock_client.config, e) for e in not_req_enh]))
self.assertTrue(
"example.com" in mock_client.enhance_config.call_args[0][0])
@mock.patch('certbot.cert_manager.lineage_for_certname')
@mock.patch('certbot.main.display_ops.choose_values')
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
def test_enhance_noninteractive(self, _rec, mock_choose, mock_lineage):
mock_lineage.return_value = mock.MagicMock(
chain_path="/tmp/nonexistent")
mock_choose.return_value = ["example.com"]
with mock.patch('certbot.main.plug_sel.pick_installer'):
mock_client = self._call(['enhance', '--redirect',
'--hsts', '--non-interactive'])
self.assertTrue(mock_client.enhance_config.called)
self.assertFalse(mock_choose.called)
@mock.patch('certbot.main.display_ops.choose_values')
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
def test_user_abort_domains(self, _rec, mock_choose):
mock_choose.return_value = []
with mock.patch('certbot.main.plug_sel.pick_installer'):
self.assertRaises(errors.Error,
self._call,
['enhance', '--redirect', '--hsts'])
def test_no_enhancements_defined(self):
self.assertRaises(errors.MisconfigurationError,
self._call, ['enhance'])
@mock.patch('certbot.main.plug_sel.choose_configurator_plugins')
@mock.patch('certbot.main.display_ops.choose_values')
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
def test_plugin_selection_error(self, _rec, mock_choose, mock_pick):
mock_choose.return_value = ["example.com"]
mock_pick.return_value = (None, None)
mock_pick.side_effect = errors.PluginSelectionError()
mock_client = self._call(['enhance', '--hsts'])
self.assertFalse(mock_client.enhance_config.called)
if __name__ == '__main__':
unittest.main() # pragma: no cover