Endure incredible amounts of mockery to ensure that tests pass

This commit is contained in:
Peter Eckersley 2016-03-10 17:53:57 -08:00
parent 001c1cd835
commit 1ae8d344b0
3 changed files with 68 additions and 61 deletions

View file

@ -24,7 +24,6 @@ from letsencrypt import crypto_util
from letsencrypt import errors
from letsencrypt import interfaces
from letsencrypt import le_util
from letsencrypt import main
from letsencrypt import storage
from letsencrypt.display import ops as display_ops
@ -542,6 +541,7 @@ def renew(config, unused_plugins):
zope.component.provideUtility(lineage_config)
if should_renew(lineage_config, renewal_candidate):
plugins = plugins_disco.PluginsRegistry.find_all()
from letsencrypt import main
main.obtain_cert(lineage_config, plugins, renewal_candidate)
renew_successes.append(renewal_candidate.fullchain)
else:
@ -612,7 +612,6 @@ class SilentParser(object): # pylint: disable=too-few-public-methods
kwargs["help"] = argparse.SUPPRESS
self.parser.add_argument(*args, **kwargs)
class HelpfulArgumentParser(object):
"""Argparse Wrapper.
@ -622,19 +621,16 @@ class HelpfulArgumentParser(object):
"""
# Maps verbs/subcommands to the functions that implement them
VERBS = {"auth": main.obtain_cert, "certonly": main.obtain_cert,
"config_changes": main.config_changes, "everything": main.run,
"install": main.install, "plugins": main.plugins_cmd, "renew": renew,
"revoke": main.revoke, "rollback": main.rollback, "run": main.run}
# List of topics for which additional help can be provided
HELP_TOPICS = ["all", "security",
"paths", "automation", "testing"] + VERBS.keys()
def __init__(self, args, plugins, detect_defaults=False):
from letsencrypt import main
self.VERBS = main.VERBS
# List of topics for which additional help can be provided
HELP_TOPICS = ["all", "security",
"paths", "automation", "testing"] + main.VERBS.keys()
plugin_names = [name for name, _p in plugins.iteritems()]
self.help_topics = self.HELP_TOPICS + plugin_names + [None]
self.help_topics = HELP_TOPICS + plugin_names + [None]
usage, short_usage = usage_strings(plugins)
self.parser = configargparse.ArgParser(
usage=short_usage,

View file

@ -34,6 +34,7 @@ import OpenSSL
logger = logging.getLogger(__name__)
def _suggest_donation_if_appropriate(config, action):
"""Potentially suggest a donation to support Let's Encrypt."""
if config.staging or config.verb == "renew":
@ -698,6 +699,16 @@ def main(cli_args=sys.argv[1:]):
return config.func(config, plugins)
# Maps verbs/subcommands to the functions that implement them
# In principle this should live in cli.HelpfulArgumentParser, but
# due to issues with import cycles and testing, it lives here
VERBS = {"auth": obtain_cert, "certonly": obtain_cert,
"config_changes": config_changes, "everything": run,
"install": install, "plugins": plugins_cmd, "renew": cli.renew,
"revoke": revoke, "rollback": rollback, "run": run}
if __name__ == "__main__":
err_string = main()
if err_string:

View file

@ -52,15 +52,15 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
def _call(self, args):
"Run the cli with output streams and actual client mocked out"
with mock.patch('letsencrypt.cli.client') as client:
with mock.patch('letsencrypt.main.client') as client:
ret, stdout, stderr = self._call_no_clientmock(args)
return ret, stdout, stderr, client
def _call_no_clientmock(self, args):
"Run the client with output streams mocked out"
args = self.standard_args + args
with mock.patch('letsencrypt.cli.sys.stdout') as stdout:
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
with mock.patch('letsencrypt.main.sys.stdout') as stdout:
with mock.patch('letsencrypt.main.sys.stderr') as stderr:
ret = main.main(args[:]) # NOTE: parser can alter its args!
return ret, stdout, stderr
@ -70,8 +70,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
caller.
"""
args = self.standard_args + args
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
with mock.patch('letsencrypt.cli.client') as client:
with mock.patch('letsencrypt.main.sys.stderr') as stderr:
with mock.patch('letsencrypt.main.client') as client:
ret = main.main(args[:]) # NOTE: parser can alter its args!
return ret, None, stderr, client
@ -83,7 +83,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
def _help_output(self, args):
"Run a command, and return the ouput string for scrutiny"
output = StringIO.StringIO()
with mock.patch('letsencrypt.cli.sys.stdout', new=output):
with mock.patch('letsencrypt.main.sys.stdout', new=output):
self.assertRaises(SystemExit, self._call_stdout, args)
out = output.getvalue()
return out
@ -136,7 +136,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
"Ensure that a particular error raises a missing cli flag error containing message"
exc = None
try:
with mock.patch('letsencrypt.cli.sys.stderr'):
with mock.patch('letsencrypt.main.sys.stderr'):
main.main(self.standard_args + args[:]) # NOTE: parser can alter its args!
except errors.MissingCommandlineFlag as exc:
self.assertTrue(message in str(exc))
@ -147,15 +147,15 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._cli_missing_flag(args, "specify a plugin")
args.extend(['--standalone', '-d', 'eg.is'])
self._cli_missing_flag(args, "register before running")
with mock.patch('letsencrypt.cli._auth_from_domains'):
with mock.patch('letsencrypt.cli.client.acme_from_config_key'):
with mock.patch('letsencrypt.main._auth_from_domains'):
with mock.patch('letsencrypt.main.client.acme_from_config_key'):
args.extend(['--email', 'io@io.is'])
self._cli_missing_flag(args, "--agree-tos")
@mock.patch('letsencrypt.cli.client.acme_client.Client')
@mock.patch('letsencrypt.cli._determine_account')
@mock.patch('letsencrypt.cli.client.Client.obtain_and_enroll_certificate')
@mock.patch('letsencrypt.cli._auth_from_domains')
@mock.patch('letsencrypt.main.client.acme_client.Client')
@mock.patch('letsencrypt.main._determine_account')
@mock.patch('letsencrypt.main.client.Client.obtain_and_enroll_certificate')
@mock.patch('letsencrypt.main._auth_from_domains')
def test_user_agent(self, afd, _obt, det, _client):
# Normally the client is totally mocked out, but here we need more
# arguments to automate it...
@ -164,7 +164,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
det.return_value = mock.MagicMock(), None
afd.return_value = mock.MagicMock(), "newcert"
with mock.patch('letsencrypt.cli.client.acme_client.ClientNetwork') as acme_net:
with mock.patch('letsencrypt.main.client.acme_client.ClientNetwork') as acme_net:
self._call_no_clientmock(args)
os_ver = " ".join(le_util.get_os_info())
ua = acme_net.call_args[1]["user_agent"]
@ -174,7 +174,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
if "linux" in plat.lower():
self.assertTrue(platform.linux_distribution()[0] in ua)
with mock.patch('letsencrypt.cli.client.acme_client.ClientNetwork') as acme_net:
with mock.patch('letsencrypt.main.client.acme_client.ClientNetwork') as acme_net:
ua = "bandersnatch"
args += ["--user-agent", ua]
self._call_no_clientmock(args)
@ -197,8 +197,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertEqual(args.chain_path, os.path.abspath(chain))
self.assertEqual(args.fullchain_path, os.path.abspath(fullchain))
@mock.patch('letsencrypt.cli.record_chosen_plugins')
@mock.patch('letsencrypt.cli.display_ops')
@mock.patch('letsencrypt.main.cli.record_chosen_plugins')
@mock.patch('letsencrypt.main.cli.display_ops')
def test_installer_selection(self, mock_display_ops, _rec):
self._call(['install', '--domains', 'foo.bar', '--cert-path', 'cert',
'--key-path', 'key', '--chain-path', 'chain'])
@ -237,8 +237,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._cli_missing_flag(["--standalone"], "With the standalone plugin, you probably")
with mock.patch("letsencrypt.cli._init_le_client") as mock_init:
with mock.patch("letsencrypt.cli._auth_from_domains") as mock_afd:
with mock.patch("letsencrypt.main._init_le_client") as mock_init:
with mock.patch("letsencrypt.main._auth_from_domains") as mock_afd:
mock_afd.return_value = (mock.MagicMock(), mock.MagicMock())
self._call(["certonly", "--manual", "-d", "foo.bar"])
unused_config, auth, unused_installer = mock_init.call_args[0]
@ -267,8 +267,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
for r in xrange(len(flags)))):
self._call(['plugins'] + list(args))
@mock.patch('letsencrypt.cli.plugins_disco')
@mock.patch('letsencrypt.cli.HelpfulArgumentParser.determine_help_topics')
@mock.patch('letsencrypt.main.plugins_disco')
@mock.patch('letsencrypt.main.cli.HelpfulArgumentParser.determine_help_topics')
def test_plugins_no_args(self, _det, mock_disco):
ifaces = []
plugins = mock_disco.PluginsRegistry.find_all()
@ -279,8 +279,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
filtered = plugins.visible().ifaces()
stdout.write.called_once_with(str(filtered))
@mock.patch('letsencrypt.cli.plugins_disco')
@mock.patch('letsencrypt.cli.HelpfulArgumentParser.determine_help_topics')
@mock.patch('letsencrypt.main.plugins_disco')
@mock.patch('letsencrypt.main.cli.HelpfulArgumentParser.determine_help_topics')
def test_plugins_init(self, _det, mock_disco):
ifaces = []
plugins = mock_disco.PluginsRegistry.find_all()
@ -294,8 +294,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
verified = filtered.verify()
stdout.write.called_once_with(str(verified))
@mock.patch('letsencrypt.cli.plugins_disco')
@mock.patch('letsencrypt.cli.HelpfulArgumentParser.determine_help_topics')
@mock.patch('letsencrypt.main.plugins_disco')
@mock.patch('letsencrypt.main.cli.HelpfulArgumentParser.determine_help_topics')
def test_plugins_prepare(self, _det, mock_disco):
ifaces = []
plugins = mock_disco.PluginsRegistry.find_all()
@ -504,9 +504,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
{"eg.com": "/tmp", "www.eg.com": "/tmp", "eg.is": "/tmp2"})
def _certonly_new_request_common(self, mock_client, args=None):
with mock.patch('letsencrypt.cli._treat_as_renewal') as mock_renewal:
with mock.patch('letsencrypt.main._treat_as_renewal') as mock_renewal:
mock_renewal.return_value = ("newcert", None)
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
with mock.patch('letsencrypt.main._init_le_client') as mock_init:
mock_init.return_value = mock_client
if args is None:
args = []
@ -563,17 +563,17 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_client.obtain_certificate.return_value = (mock_certr, 'chain',
mock_key, 'csr')
try:
with mock.patch('letsencrypt.cli._find_duplicative_certs') as mock_fdc:
with mock.patch('letsencrypt.main._find_duplicative_certs') as mock_fdc:
mock_fdc.return_value = (mock_lineage, None)
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
with mock.patch('letsencrypt.main._init_le_client') as mock_init:
mock_init.return_value = mock_client
get_utility_path = 'letsencrypt.cli.zope.component.getUtility'
get_utility_path = 'letsencrypt.main.zope.component.getUtility'
with mock.patch(get_utility_path) as mock_get_utility:
with mock.patch('letsencrypt.cli.OpenSSL') as mock_ssl:
with mock.patch('letsencrypt.main.OpenSSL') as mock_ssl:
mock_latest = mock.MagicMock()
mock_latest.get_issuer.return_value = "Fake fake"
mock_ssl.crypto.load_certificate.return_value = mock_latest
with mock.patch('letsencrypt.cli.crypto_util'):
with mock.patch('letsencrypt.main.crypto_util'):
if not args:
args = ['-d', 'isnot.org', '-a', 'standalone', 'certonly']
if extra_args:
@ -689,7 +689,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
if names is not None:
mock_lineage.names.return_value = names
mock_rc.return_value = mock_lineage
with mock.patch('letsencrypt.cli.obtain_cert') as mock_obtain_cert:
with mock.patch('letsencrypt.main.obtain_cert') as mock_obtain_cert:
self._test_renewal_common(True, None, error_expected=error_expected,
args=['renew'], renew=False)
if assert_oc_called is not None:
@ -738,7 +738,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_rc.return_value = mock_lineage
mock_lineage.configuration = {
'renewalparams': {'authenticator': 'webroot'}}
with mock.patch('letsencrypt.cli.obtain_cert') as mock_obtain_cert:
with mock.patch('letsencrypt.main.obtain_cert') as mock_obtain_cert:
mock_obtain_cert.side_effect = Exception
self._test_renewal_common(True, None, error_expected=True,
args=['renew'], renew=False)
@ -750,8 +750,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
renew=False, error_expected=True)
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._treat_as_renewal')
@mock.patch('letsencrypt.cli._init_le_client')
@mock.patch('letsencrypt.main._treat_as_renewal')
@mock.patch('letsencrypt.main._init_le_client')
def test_certonly_reinstall(self, mock_init, mock_renewal, mock_get_utility):
mock_renewal.return_value = ('reinstall', mock.MagicMock())
mock_init.return_value = mock_client = mock.MagicMock()
@ -768,9 +768,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_client.obtain_certificate_from_csr.return_value = (certr, chain)
cert_path = '/etc/letsencrypt/live/example.com/cert.pem'
mock_client.save_certificate.return_value = cert_path, None, None
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
with mock.patch('letsencrypt.main._init_le_client') as mock_init:
mock_init.return_value = mock_client
get_utility_path = 'letsencrypt.cli.zope.component.getUtility'
get_utility_path = 'letsencrypt.main.zope.component.getUtility'
with mock.patch(get_utility_path) as mock_get_utility:
chain_path = '/etc/letsencrypt/live/example.com/chain.pem'
full_path = '/etc/letsencrypt/live/example.com/fullchain.pem'
@ -779,7 +779,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
CSR, cert_path, chain_path, full_path).split()
if extra_args:
args += extra_args
with mock.patch('letsencrypt.cli.crypto_util'):
with mock.patch('letsencrypt.main.crypto_util'):
self._call(args)
if '--dry-run' in args:
@ -803,7 +803,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertTrue(
'dry run' in mock_get_utility().add_message.call_args[0][0])
@mock.patch('letsencrypt.cli.client.acme_client')
@mock.patch('letsencrypt.main.client.acme_client')
def test_revoke_with_key(self, mock_acme_client):
server = 'foo.bar'
self._call_no_clientmock(['--cert-path', CERT, '--key-path', KEY,
@ -816,7 +816,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_revoke = mock_acme_client.Client().revoke
mock_revoke.assert_called_once_with(jose.ComparableX509(cert))
@mock.patch('letsencrypt.cli._determine_account')
@mock.patch('letsencrypt.main._determine_account')
def test_revoke_without_key(self, mock_determine_account):
mock_determine_account.return_value = (mock.MagicMock(), None)
_, _, _, client = self._call(['--cert-path', CERT, 'revoke'])
@ -825,7 +825,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_revoke = client.acme_from_config_key().revoke
mock_revoke.assert_called_once_with(jose.ComparableX509(cert))
@mock.patch('letsencrypt.cli.sys')
@mock.patch('letsencrypt.main.sys')
def test_handle_exception(self, mock_sys):
# pylint: disable=protected-access
from acme import messages
@ -833,7 +833,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
config = mock.MagicMock()
mock_open = mock.mock_open()
with mock.patch('letsencrypt.cli.open', mock_open, create=True):
with mock.patch('letsencrypt.main.open', mock_open, create=True):
exception = Exception('detail')
config.verbose_count = 1
main._handle_exception(
@ -843,7 +843,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
error_msg = mock_sys.exit.call_args_list[0][0][0]
self.assertTrue('unexpected error' in error_msg)
with mock.patch('letsencrypt.cli.open', mock_open, create=True):
with mock.patch('letsencrypt.main.open', mock_open, create=True):
mock_open.side_effect = [KeyboardInterrupt]
error = errors.Error('detail')
main._handle_exception(
@ -908,7 +908,7 @@ class DetermineAccountTest(unittest.TestCase):
def _call(self):
# pylint: disable=protected-access
from letsencrypt.main import _determine_account
with mock.patch('letsencrypt.cli.account.AccountFileStorage') as mock_storage:
with mock.patch('letsencrypt.main.account.AccountFileStorage') as mock_storage:
mock_storage.return_value = self.account_storage
return _determine_account(self.config)
@ -940,7 +940,7 @@ class DetermineAccountTest(unittest.TestCase):
def test_no_accounts_no_email(self, mock_get_email):
mock_get_email.return_value = 'foo@bar.baz'
with mock.patch('letsencrypt.cli.client') as client:
with mock.patch('letsencrypt.main.client') as client:
client.register.return_value = (
self.accs[0], mock.sentinel.acme)
self.assertEqual((self.accs[0], mock.sentinel.acme), self._call())
@ -952,7 +952,7 @@ class DetermineAccountTest(unittest.TestCase):
def test_no_accounts_email(self):
self.config.email = 'other email'
with mock.patch('letsencrypt.cli.client') as client:
with mock.patch('letsencrypt.main.client') as client:
client.register.return_value = (self.accs[1], mock.sentinel.acme)
self._call()
self.assertEqual(self.accs[1].id, self.config.account)
@ -1014,7 +1014,7 @@ class MockedVerb(object):
"""
def __init__(self, verb_name):
self.verb_dict = cli.HelpfulArgumentParser.VERBS
self.verb_dict = main.VERBS
self.verb_func = None
self.verb_name = verb_name