second round revisions

This commit is contained in:
James Kasten
2015-02-23 23:18:07 -08:00
parent fa0c3d2b9f
commit f5c30b383a
14 changed files with 126 additions and 105 deletions
+9 -6
View File
@@ -65,6 +65,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:ivar config: Configuration. :ivar config: Configuration.
:type config: :class:`~letsencrypt.client.interfaces.IConfig` :type config: :class:`~letsencrypt.client.interfaces.IConfig`
:ivar parser: Handles low level parsing
:type parser: :class:`letsencrypt.client.apache.parser`
:ivar tup version: version of Apache :ivar tup version: version of Apache
:ivar list vhosts: All vhosts found in the configuration :ivar list vhosts: All vhosts found in the configuration
(:class:`list` of :class:`letsencrypt.client.apache.obj.VirtualHost`) (:class:`list` of :class:`letsencrypt.client.apache.obj.VirtualHost`)
@@ -92,13 +95,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Add name_server association dict # Add name_server association dict
self.assoc = dict() self.assoc = dict()
# Add number of outstanding challenges # Add number of outstanding challenges
self.chall_out = 0 self._chall_out = 0
# These will be set in the prepare function # These will be set in the prepare function
self.parser = None self.parser = None
self.version = version self.version = version
self.vhosts = None self.vhosts = None
self.enhance_func = {"redirect": self._enable_redirect} self._enhance_func = {"redirect": self._enable_redirect}
def prepare(self): def prepare(self):
"""Prepare the authenticator/installer.""" """Prepare the authenticator/installer."""
@@ -533,7 +536,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
""" """
try: try:
return self.enhance_func[enhancement]( return self._enhance_func[enhancement](
self.choose_vhost(domain), options) self.choose_vhost(domain), options)
except ValueError: except ValueError:
raise errors.LetsEncryptConfiguratorError( raise errors.LetsEncryptConfiguratorError(
@@ -987,7 +990,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:rtype: list :rtype: list
""" """
self.chall_out += len(chall_list) self._chall_out += len(chall_list)
responses = [None] * len(chall_list) responses = [None] * len(chall_list)
apache_dvsni = dvsni.ApacheDvsni(self) apache_dvsni = dvsni.ApacheDvsni(self)
@@ -1013,10 +1016,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
def cleanup(self, chall_list): def cleanup(self, chall_list):
"""Revert all challenges.""" """Revert all challenges."""
self.chall_out -= len(chall_list) self._chall_out -= len(chall_list)
# If all of the challenges have been finished, clean up everything # If all of the challenges have been finished, clean up everything
if self.chall_out <= 0: if self._chall_out <= 0:
self.revert_challenge_config() self.revert_challenge_config()
self.restart() self.restart()
+12 -10
View File
@@ -49,7 +49,8 @@ class Client(object):
:param dv_auth: IAuthenticator that can solve the :param dv_auth: IAuthenticator that can solve the
:const:`letsencrypt.client.constants.DV_CHALLENGES`. :const:`letsencrypt.client.constants.DV_CHALLENGES`.
:func:`letsencrypt.client.interfaces.IAuthenticator.prepare` The :meth:`~letsencrypt.client.interfaces.IAuthenticator.prepare`
must have already been run.
:type dv_auth: :class:`letsencrypt.client.interfaces.IAuthenticator` :type dv_auth: :class:`letsencrypt.client.interfaces.IAuthenticator`
""" """
@@ -349,14 +350,13 @@ def init_csr(privkey, names, cert_dir):
def determine_authenticator(all_auths): def determine_authenticator(all_auths):
"""Returns a valid IAuthenticator. """Returns a valid IAuthenticator.
:param list all_auths: Where each is a tuple of the form :param list all_auths: Where each is a
('description', 'IAuthenticator', *options..) where IAuthenticator is a :class:`letsencrypt.client.interfaces.IAuthenticator` object
:class:`letsencrypt.client.interfaces.IAuthenticator` object or class
and options are the parameters used to initialize the authenticator.
:returns: Valid Authenticator object or None :returns: Valid Authenticator object or None
:raises :class:`letsencrypt.client.errors.LetsEncryptClientError` :raises :class:`letsencrypt.client.errors.LetsEncryptClientError`: If no
authenticator is available.
""" """
# Available Authenticator objects # Available Authenticator objects
@@ -367,12 +367,11 @@ def determine_authenticator(all_auths):
for pot_auth in all_auths: for pot_auth in all_auths:
try: try:
pot_auth.prepare() pot_auth.prepare()
avail_auths.append(pot_auth)
except errors.LetsEncryptMisconfigurationError as err: except errors.LetsEncryptMisconfigurationError as err:
errs[pot_auth] = err errs[pot_auth] = err
avail_auths.append(pot_auth)
except errors.LetsEncryptNoInstallationError: except errors.LetsEncryptNoInstallationError:
continue continue
avail_auths.append(pot_auth)
if len(avail_auths) > 1: if len(avail_auths) > 1:
auth = display_ops.choose_authenticator(avail_auths, errs) auth = display_ops.choose_authenticator(avail_auths, errs)
@@ -396,14 +395,17 @@ def determine_installer(config):
:param config: Configuration. :param config: Configuration.
:type config: :class:`letsencrypt.client.interfaces.IConfig` :type config: :class:`letsencrypt.client.interfaces.IConfig`
:returns: IInstaller or `None`
:rtype: :class:`~letsencrypt.client.interfaces.IInstaller` or `None`
""" """
try:
installer = configurator.ApacheConfigurator(config) installer = configurator.ApacheConfigurator(config)
try:
installer.prepare() installer.prepare()
return installer return installer
except errors.LetsEncryptNoInstallationError: except errors.LetsEncryptNoInstallationError:
logging.info("Unable to find a way to install the certificate.") logging.info("Unable to find a way to install the certificate.")
return None return
except errors.LetsEncryptMisconfigurationError: except errors.LetsEncryptMisconfigurationError:
# This will have to be changed in the future... # This will have to be changed in the future...
return installer return installer
+1 -1
View File
@@ -8,7 +8,7 @@ from letsencrypt.client import interfaces
from letsencrypt.client.display import util as display_util from letsencrypt.client.display import util as display_util
# Used to make easier to read code. # Define a helper function to avoid verbose code
util = zope.component.getUtility # pylint: disable=invalid-name util = zope.component.getUtility # pylint: disable=invalid-name
+9 -8
View File
@@ -6,7 +6,7 @@ import zope.component
from letsencrypt.client import interfaces from letsencrypt.client import interfaces
from letsencrypt.client.display import util as display_util from letsencrypt.client.display import util as display_util
# Convenience call to make for easier to read lines. # Define a helper function to avoid verbose code
util = zope.component.getUtility # pylint: disable=invalid-name util = zope.component.getUtility # pylint: disable=invalid-name
@@ -47,10 +47,10 @@ def confirm_revocation(cert):
:rtype: bool :rtype: bool
""" """
text = ("Are you sure you would like to revoke the following " return util(interfaces.IDisplay).yesno(
"certificate:{0}{cert}This action cannot be " "Are you sure you would like to revoke the following "
"reversed!".format(os.linesep, cert=cert.pretty_print())) "certificate:{0}{cert}This action cannot be reversed!".format(
return util(interfaces.IDisplay).yesno(text) os.linesep, cert=cert.pretty_print()))
def more_info_cert(cert): def more_info_cert(cert):
@@ -59,9 +59,10 @@ def more_info_cert(cert):
:param dict cert: cert dict used throughout revoker.py :param dict cert: cert dict used throughout revoker.py
""" """
text = "Certificate Information:{0}{1}".format( util(interfaces.IDisplay).notification(
os.linesep, cert.pretty_print()) "Certificate Information:{0}{1}".format(
util(interfaces.IDisplay).notification(text, height=display_util.HEIGHT) os.linesep, cert.pretty_print()),
height=display_util.HEIGHT)
def success_revocation(cert): def success_revocation(cert):
+23 -14
View File
@@ -37,6 +37,11 @@ class NcursesDisplay(object):
# pylint: disable=unused-argument # pylint: disable=unused-argument
"""Display a notification to the user and wait for user acceptance. """Display a notification to the user and wait for user acceptance.
.. todo:: It probably makes sense to use one of the transient message
types for pause. It isn't straightforward how best to approach
the matter though given the context of our messages.
http://pythondialog.sourceforge.net/doc/widgets.html#displaying-transient-messages
:param str message: Message to display :param str message: Message to display
:param int height: Height of the dialog box :param int height: Height of the dialog box
:param bool pause: Not applicable to NcursesDisplay :param bool pause: Not applicable to NcursesDisplay
@@ -63,15 +68,21 @@ class NcursesDisplay(object):
:rtype: tuple :rtype: tuple
""" """
help_button = bool(help_label) menu_options = {
"choices": choices,
"ok_label": ok_label,
"cancel_label": cancel_label,
"help_button": bool(help_label),
"help_label": help_label,
"width": self.width,
"height": self.height,
"menu_height": self.height-6,
}
# Can accept either tuples or just the actual choices # Can accept either tuples or just the actual choices
if choices and isinstance(choices[0], tuple): if choices and isinstance(choices[0], tuple):
code, selection = self.dialog.menu( # pylint: disable=star-args
message, choices=choices, ok_label=ok_label, code, selection = self.dialog.menu(message, **menu_options)
cancel_label=cancel_label,
help_button=help_button, help_label=help_label,
width=self.width, height=self.height)
# Return the selection index # Return the selection index
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
@@ -81,14 +92,12 @@ class NcursesDisplay(object):
return code, -1 return code, -1
else: else:
choices = [ # "choices" is not formatted the way the dialog.menu expects...
menu_options["choices"] = [
(str(i), choice) for i, choice in enumerate(choices, 1) (str(i), choice) for i, choice in enumerate(choices, 1)
] ]
code, tag = self.dialog.menu( # pylint: disable=star-args
message, choices=choices, ok_label=ok_label, code, tag = self.dialog.menu(message, **menu_options)
cancel_label=cancel_label,
help_button=help_button, help_label=help_label,
width=self.width, height=self.height)
if code == CANCEL: if code == CANCEL:
return code, -1 return code, -1
@@ -263,8 +272,8 @@ class FileDisplay(object):
while True: while True:
self._print_menu(message, tags) self._print_menu(message, tags)
code, ans = self.input("Select the appropriate numbers " code, ans = self.input("Select the appropriate numbers separated "
"separated by commas and/or spaces ") "by commas and/or spaces")
if code == OK: if code == OK:
indices = separate_list_input(ans) indices = separate_list_input(ans)
+11 -7
View File
@@ -18,9 +18,11 @@ class IAuthenticator(zope.interface.Interface):
Finish up any additional initialization. Finish up any additional initialization.
:raises :raises :class:`~.errors.LetsEncryptMisconfigurationError`: when
:class:`letsencrypt.client.errors.LetsEncryptMisconfigurationError` full initialization cannot be completed.
when full initialization cannot be completed. :raises :class:`~.errors.LetsEncryptNoInstallationError`: when the
necessary programs/files cannot be located.
""" """
def get_chall_pref(domain): def get_chall_pref(domain):
@@ -133,9 +135,11 @@ class IInstaller(zope.interface.Interface):
Finish up any additional initialization. Finish up any additional initialization.
:raises :raises :class:`~.errors.LetsEncryptMisconfigurationError`: when
:class:`letsencrypt.client.errors.LetsEncryptMisconfigurationError` full initialization cannot be completed.
when full initialization cannot be completed. :raises :class:`~.errors.LetsEncryptNoInstallationError`: when the
necessary programs/files cannot be located.
""" """
def get_all_names(): def get_all_names():
@@ -247,7 +251,7 @@ class IDisplay(zope.interface.Interface):
""" """
def input(message): def input(message):
"""Accept input from the user """Accept input from the user.
:param str message: message to display to the user :param str message: message to display to the user
+25 -24
View File
@@ -191,12 +191,12 @@ class Revoker(object):
try: try:
cert_sha1 = M2Crypto.X509.load_cert( cert_sha1 = M2Crypto.X509.load_cert(
cert_path).get_fingerprint(md="sha1") cert_path).get_fingerprint(md="sha1")
except (IOError, M2Crypto.X509.X509Error):
continue
if cert_sha1 in csha1_vhlist: if cert_sha1 in csha1_vhlist:
csha1_vhlist[cert_sha1].append(path) csha1_vhlist[cert_sha1].append(path)
else: else:
csha1_vhlist[cert_sha1] = [path] csha1_vhlist[cert_sha1] = [path]
except (IOError, M2Crypto.X509.X509Error):
continue
return csha1_vhlist return csha1_vhlist
@@ -216,9 +216,9 @@ class Revoker(object):
if self.no_confirm or revocation.confirm_revocation(cert): if self.no_confirm or revocation.confirm_revocation(cert):
try: try:
self._acme_revoke(cert) self._acme_revoke(cert)
success_list.append(cert) success_list.append(cert)
revocation.success_revocation(cert) revocation.success_revocation(cert)
except errors.LetsEncryptClientError: except errors.LetsEncryptClientError:
# TODO: Improve error handling when networking is set... # TODO: Improve error handling when networking is set...
logging.error( logging.error(
@@ -238,19 +238,21 @@ class Revoker(object):
:returns: TODO :returns: TODO
""" """
# These will both have to change in the future away from M2Crypto
# pylint: disable=protected-access
certificate = acme_util.ComparableX509(cert._cert)
try: try:
certificate = acme_util.ComparableX509(cert.cert)
with open(cert.backup_key_path, "rU") as backup_key_file: with open(cert.backup_key_path, "rU") as backup_key_file:
key = Crypto.PublicKey.RSA.importKey(backup_key_file.read()) key = Crypto.PublicKey.RSA.importKey(backup_key_file.read())
# If the key file doesn't exist... or is corrupted # If the key file doesn't exist... or is corrupted
except (OSError, IOError): except (IndexError, ValueError, TypeError):
raise errors.LetsEncryptRevokerError("Unable to read key file") raise errors.LetsEncryptRevokerError(
"Corrupted backup key file: %s" % cert.backup_key_path)
# TODO: Catch error associated with already revoked and proceed. # TODO: Catch error associated with already revoked and proceed.
return self.network.send_and_receive_expected( return self.network.send_and_receive_expected(
messages.RevocationRequest.create( messages.RevocationRequest.create(certificate=certificate, key=key),
certificate=certificate, key=key),
messages.Revocation) messages.Revocation)
def _remove_certs_keys(self, cert_list): # pylint: disable=no-self-use def _remove_certs_keys(self, cert_list): # pylint: disable=no-self-use
@@ -365,8 +367,8 @@ class Revoker(object):
class Cert(object): class Cert(object):
"""Cert object used for Revocation convenience. """Cert object used for Revocation convenience.
:ivar cert: M2Crypto X509 cert :ivar _cert: M2Crypto X509 cert
:type cert: :class:`M2Crypto.X509` :type _cert: :class:`M2Crypto.X509`
:ivar int idx: convenience index used for listing :ivar int idx: convenience index used for listing
:ivar orig: (`str` path - original certificate, `str` status) :ivar orig: (`str` path - original certificate, `str` status)
@@ -394,7 +396,7 @@ class Cert(object):
""" """
try: try:
self.cert = M2Crypto.X509.load_cert(cert_path) self._cert = M2Crypto.X509.load_cert(cert_path)
except (IOError, M2Crypto.X509.X509Error): except (IOError, M2Crypto.X509.X509Error):
raise errors.LetsEncryptRevokerError( raise errors.LetsEncryptRevokerError(
"Error loading certificate: %s" % cert_path) "Error loading certificate: %s" % cert_path)
@@ -464,27 +466,26 @@ class Cert(object):
self.backup_path = backup self.backup_path = backup
self.backup_key_path = backup_key self.backup_key_path = backup_key
# I would rather not have outside classes messing with Cert directly. # M2Crypto is eventually going to be replaced, hence the reason for _cert
# (I would like to change M2Crypto -> something else without issues)
def get_cn(self): def get_cn(self):
"""Get common name.""" """Get common name."""
return self.cert.get_subject().CN return self._cert.get_subject().CN
def get_fingerprint(self): def get_fingerprint(self):
"""Get SHA1""" """Get SHA1 fingerprint."""
return self.cert.get_fingerprint(md="sha1") return self._cert.get_fingerprint(md="sha1")
def get_not_before(self): def get_not_before(self):
"""Get not_valid_before field.""" """Get not_valid_before field."""
return self.cert.get_not_before().get_datetime() return self._cert.get_not_before().get_datetime()
def get_not_after(self): def get_not_after(self):
"""Get not_valid_after field.""" """Get not_valid_after field."""
return self.cert.get_not_after().get_datetime() return self._cert.get_not_after().get_datetime()
def get_der(self): def get_der(self):
"""Get certificate in der format.""" """Get certificate in der format."""
return self.cert.as_der() return self._cert.as_der()
def get_pub_key(self): def get_pub_key(self):
"""Get public key size. """Get public key size.
@@ -492,24 +493,24 @@ class Cert(object):
.. todo:: M2Crypto doesn't support ECC, this will have to be updated .. todo:: M2Crypto doesn't support ECC, this will have to be updated
""" """
return "RSA " + str(self.cert.get_pubkey().size() * 8) return "RSA " + str(self._cert.get_pubkey().size() * 8)
def get_san(self): def get_san(self):
"""Get subject alternative name if available.""" """Get subject alternative name if available."""
try: try:
return self.cert.get_ext("subjectAltName").get_value() return self._cert.get_ext("subjectAltName").get_value()
except LookupError: except LookupError:
return "" return ""
def __str__(self): def __str__(self):
text = [ text = [
"Subject: %s" % self.cert.get_subject().as_text(), "Subject: %s" % self._cert.get_subject().as_text(),
"SAN: %s" % self.get_san(), "SAN: %s" % self.get_san(),
"Issuer: %s" % self.cert.get_issuer().as_text(), "Issuer: %s" % self._cert.get_issuer().as_text(),
"Public Key: %s" % self.get_pub_key(), "Public Key: %s" % self.get_pub_key(),
"Not Before: %s" % str(self.get_not_before()), "Not Before: %s" % str(self.get_not_before()),
"Not After: %s" % str(self.get_not_after()), "Not After: %s" % str(self.get_not_after()),
"Serial Number: %s" % self.cert.get_serial_number(), "Serial Number: %s" % self._cert.get_serial_number(),
"SHA1: %s%s" % (self.get_fingerprint(), os.linesep), "SHA1: %s%s" % (self.get_fingerprint(), os.linesep),
"Installed: %s" % ", ".join(self.installed), "Installed: %s" % ", ".join(self.installed),
] ]
@@ -47,7 +47,6 @@ class StandaloneAuthenticator(object):
.. todo:: This should probably do the port check .. todo:: This should probably do the port check
""" """
pass
def client_signal_handler(self, sig, unused_frame): def client_signal_handler(self, sig, unused_frame):
"""Signal handler for the parent process. """Signal handler for the parent process.
+10 -10
View File
@@ -9,8 +9,8 @@ from letsencrypt.client import errors
class DetermineAuthenticatorTest(unittest.TestCase): class DetermineAuthenticatorTest(unittest.TestCase):
def setUp(self): def setUp(self):
from letsencrypt.client.apache.configurator import ApacheConfigurator from letsencrypt.client.apache.configurator import ApacheConfigurator
from letsencrypt.client.standalone_authenticator \ from letsencrypt.client.standalone_authenticator import (
import StandaloneAuthenticator StandaloneAuthenticator)
self.mock_stand = mock.MagicMock( self.mock_stand = mock.MagicMock(
spec=StandaloneAuthenticator, description="Apache Web Server") spec=StandaloneAuthenticator, description="Apache Web Server")
@@ -37,16 +37,16 @@ class DetermineAuthenticatorTest(unittest.TestCase):
self._call(self.all_auths[:1]), self.mock_apache) self._call(self.all_auths[:1]), self.mock_apache)
def test_no_installation_one(self): def test_no_installation_one(self):
self.mock_apache.prepare.side_effect = \ self.mock_apache.prepare.side_effect = (
errors.LetsEncryptNoInstallationError errors.LetsEncryptNoInstallationError)
self.assertEqual(self._call(self.all_auths), self.mock_stand) self.assertEqual(self._call(self.all_auths), self.mock_stand)
def test_no_installations(self): def test_no_installations(self):
self.mock_apache.prepare.side_effect = \ self.mock_apache.prepare.side_effect = (
errors.LetsEncryptNoInstallationError errors.LetsEncryptNoInstallationError)
self.mock_stand.prepare.side_effect = \ self.mock_stand.prepare.side_effect = (
errors.LetsEncryptNoInstallationError errors.LetsEncryptNoInstallationError)
self.assertRaises(errors.LetsEncryptClientError, self.assertRaises(errors.LetsEncryptClientError,
self._call, self._call,
@@ -55,8 +55,8 @@ class DetermineAuthenticatorTest(unittest.TestCase):
@mock.patch("letsencrypt.client.client.logging") @mock.patch("letsencrypt.client.client.logging")
@mock.patch("letsencrypt.client.client.display_ops.choose_authenticator") @mock.patch("letsencrypt.client.client.display_ops.choose_authenticator")
def test_misconfigured(self, mock_choose, unused_log): def test_misconfigured(self, mock_choose, unused_log):
self.mock_apache.prepare.side_effect = \ self.mock_apache.prepare.side_effect = (
errors.LetsEncryptMisconfigurationError errors.LetsEncryptMisconfigurationError)
mock_choose.return_value = self.mock_apache mock_choose.return_value = self.mock_apache
self.assertRaises(SystemExit, self._call, self.all_auths) self.assertRaises(SystemExit, self._call, self.all_auths)
@@ -35,7 +35,7 @@ class DisplayCertsTest(unittest.TestCase):
code, choice = self._call(self.certs) code, choice = self._call(self.certs)
self.assertEqual(display_util.OK, code) self.assertEqual(display_util.OK, code)
self.assertTrue(self.certs[choice] == self.cert0) self.assertEqual(self.certs[choice], self.cert0)
@mock.patch("letsencrypt.client.display.revocation.util") @mock.patch("letsencrypt.client.display.revocation.util")
def test_cancel(self, mock_util): def test_cancel(self, mock_util):
+17 -15
View File
@@ -42,6 +42,18 @@ class NcursesDisplayTest(DisplayT):
super(NcursesDisplayTest, self).setUp() super(NcursesDisplayTest, self).setUp()
self.displayer = display_util.NcursesDisplay() self.displayer = display_util.NcursesDisplay()
self.default_menu_options = {
"choices": self.choices,
"ok_label": "OK",
"cancel_label": "Cancel",
"help_button": False,
"help_label": "",
"width": display_util.WIDTH,
"height": display_util.HEIGHT,
"menu_height": display_util.HEIGHT-6,
}
@mock.patch("letsencrypt.client.display.util.dialog.Dialog.msgbox") @mock.patch("letsencrypt.client.display.util.dialog.Dialog.msgbox")
def test_notification(self, mock_msgbox): def test_notification(self, mock_msgbox):
"""Kind of worthless... one liner.""" """Kind of worthless... one liner."""
@@ -53,11 +65,7 @@ class NcursesDisplayTest(DisplayT):
mock_menu.return_value = (display_util.OK, "First") mock_menu.return_value = (display_util.OK, "First")
ret = self.displayer.menu("Message", self.choices) ret = self.displayer.menu("Message", self.choices)
mock_menu.assert_called_with( mock_menu.assert_called_with("Message", **self.default_menu_options)
"Message", choices=self.choices, ok_label="OK",
cancel_label="Cancel",
help_button=False, help_label="",
width=display_util.WIDTH, height=display_util.HEIGHT)
self.assertEqual(ret, (display_util.OK, 0)) self.assertEqual(ret, (display_util.OK, 0))
@@ -67,11 +75,7 @@ class NcursesDisplayTest(DisplayT):
ret = self.displayer.menu("Message", self.choices) ret = self.displayer.menu("Message", self.choices)
mock_menu.assert_called_with( mock_menu.assert_called_with("Message", **self.default_menu_options)
"Message", choices=self.choices, ok_label="OK",
cancel_label="Cancel",
help_button=False, help_label="",
width=display_util.WIDTH, height=display_util.HEIGHT)
self.assertEqual(ret, (display_util.CANCEL, -1)) self.assertEqual(ret, (display_util.CANCEL, -1))
@@ -81,11 +85,9 @@ class NcursesDisplayTest(DisplayT):
ret = self.displayer.menu("Message", self.tags, help_label="More Info") ret = self.displayer.menu("Message", self.tags, help_label="More Info")
mock_menu.assert_called_with( self.default_menu_options.update(
"Message", choices=self.tags_choices, ok_label="OK", choices=self.tags_choices, help_button=True, help_label="More Info")
cancel_label="Cancel", mock_menu.assert_called_with("Message", **self.default_menu_options)
help_button=True, help_label="More Info",
width=display_util.WIDTH, height=display_util.HEIGHT)
self.assertEqual(ret, (display_util.OK, 0)) self.assertEqual(ret, (display_util.OK, 0))
+1 -1
View File
@@ -206,7 +206,7 @@ class RevokerTest(RevokerBase):
@mock.patch("letsencrypt.client.revoker.Crypto.PublicKey.RSA.importKey") @mock.patch("letsencrypt.client.revoker.Crypto.PublicKey.RSA.importKey")
def test_acme_revoke_failure(self, mock_crypto): def test_acme_revoke_failure(self, mock_crypto):
# pylint: disable=protected-access # pylint: disable=protected-access
mock_crypto.side_effect = IOError mock_crypto.side_effect = ValueError
self.assertRaises(errors.LetsEncryptClientError, self.assertRaises(errors.LetsEncryptClientError,
self.revoker._acme_revoke, self.revoker._acme_revoke,
self.certs[0]) self.certs[0])
@@ -545,8 +545,8 @@ class CleanupTest(unittest.TestCase):
class MoreInfoTest(unittest.TestCase): class MoreInfoTest(unittest.TestCase):
"""Tests for more_info() method. (trivially)""" """Tests for more_info() method. (trivially)"""
def setUp(self): def setUp(self):
from letsencrypt.client.standalone_authenticator import \ from letsencrypt.client.standalone_authenticator import (
StandaloneAuthenticator StandaloneAuthenticator)
self.authenticator = StandaloneAuthenticator() self.authenticator = StandaloneAuthenticator()
def test_more_info(self): def test_more_info(self):
@@ -557,8 +557,8 @@ class MoreInfoTest(unittest.TestCase):
class InitTest(unittest.TestCase): class InitTest(unittest.TestCase):
"""Tests for more_info() method. (trivially)""" """Tests for more_info() method. (trivially)"""
def setUp(self): def setUp(self):
from letsencrypt.client.standalone_authenticator import \ from letsencrypt.client.standalone_authenticator import (
StandaloneAuthenticator StandaloneAuthenticator)
self.authenticator = StandaloneAuthenticator() self.authenticator = StandaloneAuthenticator()
def test_prepare(self): def test_prepare(self):
+1 -1
View File
@@ -17,7 +17,7 @@ setenv =
basepython = python2.7 basepython = python2.7
commands = commands =
pip install -e .[testing] pip install -e .[testing]
python setup.py nosetests --with-coverage --cover-min-percentage=84 python setup.py nosetests --with-coverage --cover-min-percentage=83
[testenv:lint] [testenv:lint]
# recent versions of pylint do not support Python 2.6 (#97, #187) # recent versions of pylint do not support Python 2.6 (#97, #187)