mirror of
https://github.com/certbot/certbot.git
synced 2026-06-03 13:59:02 -04:00
Unittests for display_util
This commit is contained in:
parent
168a70c273
commit
01899f387e
5 changed files with 292 additions and 415 deletions
|
|
@ -1,311 +0,0 @@
|
|||
"""Lets Encrypt display."""
|
||||
import os
|
||||
import textwrap
|
||||
|
||||
import dialog
|
||||
import zope.interface
|
||||
|
||||
from letsencrypt.client import interfaces
|
||||
|
||||
|
||||
WIDTH = 72
|
||||
HEIGHT = 20
|
||||
|
||||
|
||||
class NcursesDisplay(object):
|
||||
"""Ncurses-based display."""
|
||||
|
||||
zope.interface.implements(interfaces.IDisplay)
|
||||
|
||||
def __init__(self, width=WIDTH, height=HEIGHT):
|
||||
super(NcursesDisplay, self).__init__()
|
||||
self.dialog = dialog.Dialog()
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def notification(self, message, height=10):
|
||||
"""Display a notification to the user and wait for user acceptance.
|
||||
|
||||
:param str message: Message to display
|
||||
:param int height: Height of the dialog box
|
||||
|
||||
"""
|
||||
self.dialog.msgbox(message, height, width=self.width)
|
||||
|
||||
def menu(self, message, choices, unused_input_text="",
|
||||
ok_label="OK", help_label=""):
|
||||
"""Display a menu.
|
||||
|
||||
:param str message: title of menu
|
||||
:param choices: menu lines
|
||||
:type choices: list of tuples (tag, item) or
|
||||
list of items (tags will be enumerated)
|
||||
|
||||
:returns: tuple of the form (code, tag) where
|
||||
code is a display exit code
|
||||
tag is the tag string corresponding to the item chosen
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
if help_label:
|
||||
help_button = True
|
||||
else:
|
||||
help_button = False
|
||||
|
||||
# Can accept either tuples or just the actual choices
|
||||
if choices and isinstance(choices[0], tuple):
|
||||
code, selection = self.dialog.menu(
|
||||
message, choices=choices, ok_label=ok_label,
|
||||
help_button=help_button, help_label=help_label,
|
||||
width=self.width, height=self.height)
|
||||
|
||||
return code, str(selection)
|
||||
else:
|
||||
choices = list(enumerate(choices, 1))
|
||||
code, tag = self.dialog.menu(
|
||||
message, choices=choices, ok_label=ok_label,
|
||||
help_button=help_button, help_label=help_label,
|
||||
width=self.width, height=self.height)
|
||||
|
||||
return code, int(tag) - 1
|
||||
|
||||
def input(self, message):
|
||||
"""Display an input box to the user.
|
||||
|
||||
:param str message: Message to display that asks for input.
|
||||
|
||||
:returns: tuple of the form (code, string) where
|
||||
code is a display exit code
|
||||
string is the input entered by the user
|
||||
|
||||
"""
|
||||
return self.dialog.inputbox(message)
|
||||
|
||||
def yesno(self, message, yes_label="Yes", no_label="No"):
|
||||
"""Display a Yes/No dialog box
|
||||
|
||||
:param str message: message to display to user
|
||||
:param str yes_label: label on the "yes" button
|
||||
:param str no_label: label on the "no" button
|
||||
|
||||
:returns: if yes_label was selected
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
return self.dialog.DIALOG_OK == self.dialog.yesno(
|
||||
message, self.height, self.width,
|
||||
yes_label=yes_label, no_label=no_label)
|
||||
|
||||
def filter_names(self, names):
|
||||
"""Determine which names the user would like to select from a list.
|
||||
|
||||
:param list names: domain names
|
||||
|
||||
:returns: tuple of the form (code, names) where
|
||||
code is a display exit code
|
||||
names is a list of names selected
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
choices = [(n, "", 0) for n in names]
|
||||
code, names = self.dialog.checklist(
|
||||
"Which names would you like to activate HTTPS for?",
|
||||
choices=choices)
|
||||
return code, [str(s) for s in names]
|
||||
|
||||
def success_installation(self, domains):
|
||||
"""Display a box confirming the installation of HTTPS.
|
||||
|
||||
:param list domains: domain names which were enabled
|
||||
|
||||
"""
|
||||
self.dialog.msgbox(
|
||||
"\nCongratulations! You have successfully enabled "
|
||||
+ gen_https_names(domains) + "!", width=self.width)
|
||||
|
||||
|
||||
class FileDisplay(object):
|
||||
"""File-based display."""
|
||||
|
||||
zope.interface.implements(interfaces.IDisplay)
|
||||
|
||||
def __init__(self, outfile):
|
||||
super(FileDisplay, self).__init__()
|
||||
self.outfile = outfile
|
||||
|
||||
def notification(self, message, unused_height):
|
||||
"""Displays a notification and waits for user acceptance.
|
||||
|
||||
:param str message: Message to display
|
||||
|
||||
"""
|
||||
side_frame = "-" * 79
|
||||
lines = message.splitlines()
|
||||
fixed_l = []
|
||||
for line in lines:
|
||||
fixed_l.append(textwrap.fill(line, 80))
|
||||
self.outfile.write(
|
||||
"{0}{1}{0}{2}{0}{1}{0}".format(
|
||||
os.linesep, side_frame, os.linesep.join(fixed_l)))
|
||||
raw_input("Press Enter to Continue")
|
||||
|
||||
def menu(self, message, choices, input_text="",
|
||||
unused_ok_label = "", unused_help_label=""):
|
||||
"""Display a menu.
|
||||
|
||||
:param str message: title of menu
|
||||
:param choices: Menu lines
|
||||
:type choices: list of tuples (tag, item) or
|
||||
list of items (tags will be enumerated)
|
||||
|
||||
:returns: tuple of the form (code, tag) where
|
||||
code is a display exit code
|
||||
tag is the tag string corresponding to the item chosen
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
# Can take either tuples or single items in choices list
|
||||
if choices and isinstance(choices[0], tuple):
|
||||
choices = ["%s - %s" % (c[0], c[1]) for c in choices]
|
||||
|
||||
self.outfile.write("\n%s\n" % message)
|
||||
side_frame = "-" * 79
|
||||
self.outfile.write("%s\n" % side_frame)
|
||||
|
||||
for i, choice in enumerate(choices, 1):
|
||||
self.outfile.write(textwrap.fill(
|
||||
"%d: %s" % (i, choice), 80) + "\n")
|
||||
|
||||
self.outfile.write("%s\n" % side_frame)
|
||||
|
||||
code, selection = self._get_valid_int_ans(
|
||||
"%s (c to cancel): " % input_text)
|
||||
|
||||
return code, (selection - 1)
|
||||
|
||||
def input(self, message):
|
||||
# pylint: disable=no-self-use
|
||||
"""Accept input from the user
|
||||
|
||||
:param str message: message to display to the user
|
||||
|
||||
:returns: tuple of (code, input) where
|
||||
code is a display exit code
|
||||
input is a str of the user's input
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
ans = raw_input("%s (Enter c to cancel)\n" % message)
|
||||
|
||||
if ans == "c" or ans == "C":
|
||||
return CANCEL, "-1"
|
||||
else:
|
||||
return OK, ans
|
||||
|
||||
def yesno(self, message, unused_yes_label="", unused_no_label=""):
|
||||
"""Query the user with a yes/no question.
|
||||
|
||||
:param str message: question for the user
|
||||
|
||||
:returns: True for "Yes", False for "No"
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
self.outfile.write("\n%s\n" % textwrap.fill(message, 80))
|
||||
ans = raw_input("y/n: ")
|
||||
return ans.startswith("y") or ans.startswith("Y")
|
||||
|
||||
def filter_names(self, names):
|
||||
"""Determine which names the user would like to select from a list.
|
||||
|
||||
:param list names: domain names
|
||||
|
||||
:returns: tuple of the form (code, names) where
|
||||
code is a display exit code
|
||||
names is a list of names selected
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
code, tag = self.menu(
|
||||
"Choose the names would you like to upgrade to HTTPS?",
|
||||
names, "Select the number of the name: ")
|
||||
|
||||
# Make sure to return a list...
|
||||
return code, [names[tag]]
|
||||
|
||||
def success_installation(self, domains):
|
||||
"""Display a box confirming the installation of HTTPS.
|
||||
|
||||
:param list domains: domain names which were enabled
|
||||
|
||||
"""
|
||||
side_frame = '*' * 79
|
||||
msg = textwrap.fill("Congratulations! You have successfully "
|
||||
"enabled %s!" % gen_https_names(domains))
|
||||
self.outfile.write("%s\n%s\n%s\n" % (side_frame, msg, side_frame))
|
||||
|
||||
|
||||
def _get_valid_int_ans(self, input_string):
|
||||
"""Get a numerical selection.
|
||||
|
||||
:param str input_string: Instructions for the user to make a selection.
|
||||
|
||||
:returns: tuple of the form (code, selection) where
|
||||
code is a display exit code
|
||||
selection is the user"s int selection
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
valid_ans = False
|
||||
e_msg = "Make a selection by inputting the appropriate number.\n"
|
||||
while not valid_ans:
|
||||
|
||||
ans = raw_input(input_string)
|
||||
if ans.startswith("c") or ans.startswith("C"):
|
||||
code = CANCEL
|
||||
selection = -1
|
||||
valid_ans = True
|
||||
else:
|
||||
try:
|
||||
selection = int(ans)
|
||||
# TODO add check to make sure it is less than max
|
||||
if selection < 0:
|
||||
self.outfile.write(e_msg)
|
||||
continue
|
||||
code = OK
|
||||
valid_ans = True
|
||||
except ValueError:
|
||||
self.outfile.write(e_msg)
|
||||
|
||||
return code, selection
|
||||
|
||||
|
||||
# Display exit codes
|
||||
OK = "ok"
|
||||
"""Display exit code indicating user acceptance"""
|
||||
|
||||
CANCEL = "cancel"
|
||||
"""Display exit code for a user canceling the display"""
|
||||
|
||||
HELP = "help"
|
||||
"""Display exit code when for when the user requests more help."""
|
||||
|
||||
|
||||
def gen_https_names(domains):
|
||||
"""Returns a string of the https domains.
|
||||
|
||||
Domains are formatted nicely with https:// prepended to each.
|
||||
.. todo:: This should not use +=, rewrite this with unittests
|
||||
|
||||
"""
|
||||
result = ""
|
||||
if len(domains) > 2:
|
||||
for i in range(len(domains)-1):
|
||||
result = result + "https://" + domains[i] + ", "
|
||||
result = result + "and "
|
||||
if len(domains) == 2:
|
||||
return "https://" + domains[0] + " and https://" + domains[1]
|
||||
if domains:
|
||||
result = result + "https://" + domains[len(domains)-1]
|
||||
|
||||
return result
|
||||
|
|
@ -5,6 +5,7 @@ import textwrap
|
|||
import dialog
|
||||
import zope.interface
|
||||
|
||||
from letsencrypt.client import errors
|
||||
from letsencrypt.client import interfaces
|
||||
|
||||
|
||||
|
|
@ -49,8 +50,8 @@ class NcursesDisplay(object):
|
|||
|
||||
:param str message: title of menu
|
||||
|
||||
:param choices: menu lines
|
||||
:type choices: list of tuples (tag, item) or
|
||||
:param choices: menu lines, len must be > 0
|
||||
:type choices: list of tuples (`tag`, `item`) tags must be unique or
|
||||
list of items (tags will be enumerated)
|
||||
|
||||
:param str ok_label: label of the OK button
|
||||
|
|
@ -58,7 +59,7 @@ class NcursesDisplay(object):
|
|||
|
||||
:returns: tuple of the form (`code`, `tag`) where
|
||||
`code` - `str` display_util exit code
|
||||
`tag` - `str` or `int` index corresponding to the item chosen
|
||||
`tag` - `int` index corresponding to the item chosen
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
|
|
@ -75,7 +76,13 @@ class NcursesDisplay(object):
|
|||
help_button=help_button, help_label=help_label,
|
||||
width=self.width, height=self.height)
|
||||
|
||||
return code, str(selection)
|
||||
# Return the selection index
|
||||
for i, choice in enumerate(choices):
|
||||
if choice[0] == selection:
|
||||
return code, i
|
||||
|
||||
return code, -1
|
||||
|
||||
else:
|
||||
choices = [
|
||||
(str(i), choice) for i, choice in enumerate(choices, 1)
|
||||
|
|
@ -103,6 +110,8 @@ class NcursesDisplay(object):
|
|||
def yesno(self, message, yes_label="Yes", no_label="No"):
|
||||
"""Display a Yes/No dialog box
|
||||
|
||||
Yes and No label must begin with different letters.
|
||||
|
||||
:param str message: message to display to user
|
||||
:param str yes_label: label on the "yes" button
|
||||
:param str no_label: label on the "no" button
|
||||
|
|
@ -120,6 +129,7 @@ class NcursesDisplay(object):
|
|||
|
||||
:param message: Message to display before choices
|
||||
:param list tags: where each is of type :class:`str`
|
||||
len(tags) > 0
|
||||
|
||||
:returns: tuple of the form (code, list_tags) where
|
||||
`code` - int display exit code
|
||||
|
|
@ -161,7 +171,7 @@ class FileDisplay(object):
|
|||
"""Display a menu.
|
||||
|
||||
:param str message: title of menu
|
||||
:param choices: Menu lines
|
||||
:param choices: Menu lines, len must be > 0
|
||||
:type choices: list of tuples (tag, item) or
|
||||
list of descriptions (tags will be enumerated)
|
||||
|
||||
|
|
@ -175,7 +185,7 @@ class FileDisplay(object):
|
|||
|
||||
code, selection = self._get_valid_int_ans(len(choices))
|
||||
|
||||
return code, str(selection - 1)
|
||||
return code, selection - 1
|
||||
|
||||
def input(self, message):
|
||||
# pylint: disable=no-self-use
|
||||
|
|
@ -190,7 +200,7 @@ class FileDisplay(object):
|
|||
|
||||
"""
|
||||
ans = raw_input(
|
||||
textwrap.fill("%s (Enter c to cancel): " % message, 80))
|
||||
textwrap.fill("%s (Enter 'c' to cancel): " % message, 80))
|
||||
|
||||
if ans == "c" or ans == "C":
|
||||
return CANCEL, "-1"
|
||||
|
|
@ -200,6 +210,8 @@ class FileDisplay(object):
|
|||
def yesno(self, message, yes_label="Yes", no_label="No"):
|
||||
"""Query the user with a yes/no question.
|
||||
|
||||
Yes and No label must begin with different letters
|
||||
|
||||
:param str message: question for the user
|
||||
:param str yes_label: Label of the "Yes" parameter
|
||||
:param str no_label: Label of the "No" parameter
|
||||
|
|
@ -215,10 +227,9 @@ class FileDisplay(object):
|
|||
self.outfile.write("{0}{frame}{msg}{0}{frame}".format(
|
||||
os.linesep, frame=side_frame, msg=message))
|
||||
|
||||
yes_label = _parens_around_char(yes_label)
|
||||
no_label = _parens_around_char(no_label)
|
||||
|
||||
ans = raw_input("{yes}/{no}: ".format(yes=yes_label, no=no_label))
|
||||
ans = raw_input("{yes}/{no}: ".format(
|
||||
yes=_parens_around_char(yes_label),
|
||||
no=_parens_around_char(no_label)))
|
||||
|
||||
return (ans.startswith(yes_label[0].lower()) or
|
||||
ans.startswith(yes_label[0].upper()))
|
||||
|
|
@ -227,7 +238,7 @@ class FileDisplay(object):
|
|||
"""Display a checklist.
|
||||
|
||||
:param str message: Message to display to user
|
||||
:param list tags: `str` tags to select
|
||||
:param list tags: `str` tags to select, len(tags) > 0
|
||||
|
||||
:returns: tuple of (`code`, `tags`) where
|
||||
`code` - str display exit code
|
||||
|
|
@ -255,7 +266,7 @@ class FileDisplay(object):
|
|||
def _scrub_checklist_input(self, indices, tags):
|
||||
"""Validate input and transform indices to appropriate tags.
|
||||
|
||||
:param list indices: Checklist input
|
||||
:param list indices: input
|
||||
:param list tags: Original tags of the checklist
|
||||
|
||||
:returns: tags the user selected
|
||||
|
|
@ -265,7 +276,7 @@ class FileDisplay(object):
|
|||
# They should all be of type int
|
||||
try:
|
||||
indices = [int(index) for index in indices]
|
||||
except TypeError:
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
# Remove duplicates
|
||||
|
|
@ -292,14 +303,16 @@ class FileDisplay(object):
|
|||
if choices and isinstance(choices[0], tuple):
|
||||
choices = ["%s - %s" % (c[0], c[1]) for c in choices]
|
||||
|
||||
# Write out the message to the user
|
||||
self.outfile.write(
|
||||
"{new}{msg}{new}".format(new=os.linesep, msg=message))
|
||||
side_frame = ("-" * 79) + os.linesep
|
||||
self.outfile.write(side_frame)
|
||||
|
||||
for i, tag in enumerate(choices, 1):
|
||||
# Write out the menu choices
|
||||
for i, desc in enumerate(choices, 1):
|
||||
self.outfile.write(
|
||||
textwrap.fill("{num}: {tag}".format(num=i, tag=tag), 80))
|
||||
textwrap.fill("{num}: {desc}".format(num=i, desc=desc), 80))
|
||||
|
||||
# Keep this outside of the textwrap
|
||||
self.outfile.write(os.linesep)
|
||||
|
|
@ -311,7 +324,7 @@ class FileDisplay(object):
|
|||
|
||||
:param str msg: Original message
|
||||
|
||||
:returns: Formatted message
|
||||
:returns: Formatted message respecting newlines in message
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
|
@ -325,7 +338,7 @@ class FileDisplay(object):
|
|||
def _get_valid_int_ans(self, max):
|
||||
"""Get a numerical selection.
|
||||
|
||||
:param int max: The maximum entry (len of choices)
|
||||
:param int max: The maximum entry (len of choices), must be positive
|
||||
|
||||
:returns: tuple of the form (`code`, `selection`) where
|
||||
`code` - str display exit code ('ok' or cancel')
|
||||
|
|
@ -337,10 +350,10 @@ class FileDisplay(object):
|
|||
if max > 1:
|
||||
input_msg = ("Select the appropriate number "
|
||||
"[1-{max}] then [enter] (press 'c' to "
|
||||
"cancel){end}".format(max=max, end=os.linesep))
|
||||
"cancel): ".format(max=max))
|
||||
else:
|
||||
input_msg = ("Press 1 [enter] to confirm the selection "
|
||||
"(press 'c' to cancel){0}".format(os.linesep))
|
||||
"(press 'c' to cancel): ")
|
||||
while selection < 1:
|
||||
ans = raw_input(input_msg)
|
||||
if ans.startswith("c") or ans.startswith("C"):
|
||||
|
|
@ -348,6 +361,7 @@ class FileDisplay(object):
|
|||
try:
|
||||
selection = int(ans)
|
||||
if selection < 1 or selection > max:
|
||||
selection = -1
|
||||
raise ValueError
|
||||
|
||||
except ValueError:
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
"""Let's Encrypt Enhancement Display"""
|
||||
import logging
|
||||
|
||||
import zope.component
|
||||
|
||||
from letsencrypt.client import errors
|
||||
from letsencrypt.client import interfaces
|
||||
|
||||
|
||||
class EnhanceDisplay(object):
|
||||
"""Class used to display various enhancements.
|
||||
|
||||
.. note::This is not a subclass of Display. It merely uses Display as a
|
||||
component.
|
||||
|
||||
:ivar displayer: Display singleton
|
||||
:type displayer: :class:`letsencrypt.client.interfaces.IDisplay
|
||||
|
||||
:ivar dict dispatch: Dict mapping enhancements to functions
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.displayer = zope.component.getUtility(interfaces.IDisplay)
|
||||
|
||||
self.dispatch = {
|
||||
"redirect": self.redirect_by_default,
|
||||
}
|
||||
|
||||
def ask(self, enhancement):
|
||||
"""Display the enhancement to the user.
|
||||
|
||||
:param str enhancement: One of the
|
||||
:class:`letsencrypt.client.CONFIG.ENHANCEMENTS` enhancements
|
||||
|
||||
:returns: True if feature is desired, False otherwise
|
||||
:rtype: bool
|
||||
|
||||
:raises :class:`letsencrypt.client.errors.LetsEncryptClientError`: If
|
||||
the enhancement provided is not supported.
|
||||
|
||||
"""
|
||||
try:
|
||||
return self.dispatch[enhancement]
|
||||
except KeyError:
|
||||
logging.error("Unsupported enhancement given to ask()")
|
||||
raise errors.LetsEncryptClientError("Unsupported Enhancement")
|
||||
|
||||
def redirect_by_default(self):
|
||||
"""Determines whether the user would like to redirect to HTTPS.
|
||||
|
||||
:returns: True if redirect is desired, False otherwise
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
choices = [
|
||||
("Easy", "Allow both HTTP and HTTPS access to these sites"),
|
||||
("Secure", "Make all requests redirect to secure HTTPS access")]
|
||||
|
||||
result = self.displayer.menu(
|
||||
"Please choose whether HTTPS access is required or optional.",
|
||||
choices, "Please enter the appropriate number")
|
||||
|
||||
if result[0] != OK:
|
||||
return False
|
||||
|
||||
# different answer for each type of display
|
||||
return str(result[1]) == "Secure" or result[1] == 1
|
||||
|
|
@ -141,15 +141,22 @@ class IDisplay(zope.interface.Interface):
|
|||
|
||||
"""
|
||||
|
||||
def menu(message, choices, input_text="", ok_label="OK", help_label=""):
|
||||
def menu(message, choices,
|
||||
ok_label="OK", cancel_label="Cancel", help_label=""):
|
||||
"""Displays a generic menu.
|
||||
|
||||
:param str message: message to display
|
||||
|
||||
:param choices: choices
|
||||
:type choices: :class:`list` of :func:`tuple`
|
||||
:type choices: :class:`list` of :func:`tuple` or :class:`str`
|
||||
|
||||
:param str input_text: instructions on how to make a selection
|
||||
:param str ok_label: label for OK button
|
||||
:param str cancel_label: label for Cancel button
|
||||
:param str help_label: label for Help button
|
||||
|
||||
:returns: tuple of (`code`, `index`) where
|
||||
`code` - str display exit code
|
||||
`index` - int index of the user's selection
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -168,6 +175,8 @@ class IDisplay(zope.interface.Interface):
|
|||
def yesno(message, yes_label="Yes", no_label="No"):
|
||||
"""Query the user with a yes/no question.
|
||||
|
||||
Yes and No label must begin with different letters.
|
||||
|
||||
:param str message: question for the user
|
||||
|
||||
:returns: True for "Yes", False for "No"
|
||||
|
|
@ -175,13 +184,13 @@ class IDisplay(zope.interface.Interface):
|
|||
|
||||
"""
|
||||
|
||||
def checkbox(message, choices):
|
||||
def checklist(message, choices):
|
||||
"""Allow for multiple selections from a menu.
|
||||
|
||||
:param str message: message to display to the user
|
||||
|
||||
:param choices: :param choices: choices
|
||||
:type choices: :class:`list` of :func:`tuple`
|
||||
:param tags: tags
|
||||
:type tags: :class:`list` of :class:`str`
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import sys
|
||||
import contextlib
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
|
@ -10,6 +11,7 @@ class DisplayT(unittest.TestCase):
|
|||
def setUp(self):
|
||||
self.choices = [("First", "Description1"), ("Second", "Description2")]
|
||||
self.tags = ["tag1", "tag2", "tag3"]
|
||||
self.tags_choices = [("1", "tag1"), ("2", "tag2"), ("3", "tag3")]
|
||||
|
||||
|
||||
def test_visual(displayer, choices):
|
||||
|
|
@ -21,13 +23,21 @@ def test_visual(displayer, choices):
|
|||
ok_label="O", cancel_label="Can", help_label="??")
|
||||
displayer.input("Input Message")
|
||||
displayer.yesno(
|
||||
"Yes/No Message", yes_label="Yessir", no_label="Nosir")
|
||||
"YesNo Message", yes_label="Yessir", no_label="Nosir")
|
||||
displayer.checklist(
|
||||
"Checklist Message", [choice[0] for choice in choices])
|
||||
|
||||
|
||||
class NcursesDisplayTest(DisplayT):
|
||||
"""Test ncurses display."""
|
||||
"""Test ncurses display.
|
||||
|
||||
Since this is mostly a wrapper, it might be more helpful to test the actual
|
||||
dialog boxes. The test_visual function will actually display the various
|
||||
boxes but requires the user to do the verification. If something seems amiss
|
||||
please use the test_visual function to debug it, the automatic tests rely
|
||||
on too much mocking.
|
||||
|
||||
"""
|
||||
def setUp(self):
|
||||
super(NcursesDisplayTest, self).setUp()
|
||||
self.displayer = display_util.NcursesDisplay()
|
||||
|
|
@ -39,21 +49,233 @@ class NcursesDisplayTest(DisplayT):
|
|||
self.assertEqual(mock_msgbox.call_count, 1)
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util.dialog.Dialog.menu")
|
||||
def test_menu(self, mock_menu):
|
||||
pass
|
||||
def test_menu_tag_and_desc(self, mock_menu):
|
||||
mock_menu.return_value = (display_util.OK, "First")
|
||||
|
||||
def test_visual(self):
|
||||
test_visual(self.displayer, self.choices)
|
||||
ret = self.displayer.menu("Message", self.choices)
|
||||
mock_menu.assert_called_with(
|
||||
"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))
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util.dialog.Dialog.menu")
|
||||
def test_menu_tag_and_desc_cancel(self, mock_menu):
|
||||
mock_menu.return_value = (display_util.CANCEL, "")
|
||||
|
||||
ret = self.displayer.menu("Message", self.choices)
|
||||
|
||||
|
||||
mock_menu.assert_called_with(
|
||||
"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))
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util.dialog.Dialog.menu")
|
||||
def test_menu_desc_only(self, mock_menu):
|
||||
mock_menu.return_value = (display_util.OK, "1")
|
||||
|
||||
ret = self.displayer.menu("Message", self.tags, help_label="More Info")
|
||||
|
||||
|
||||
mock_menu.assert_called_with(
|
||||
"Message", choices=self.tags_choices, ok_label="OK",
|
||||
cancel_label="Cancel",
|
||||
help_button=True, help_label="More Info",
|
||||
width=display_util.WIDTH, height=display_util.HEIGHT)
|
||||
|
||||
self.assertEqual(ret, (display_util.OK, 0))
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util."
|
||||
"dialog.Dialog.inputbox")
|
||||
def test_input(self, mock_input):
|
||||
self.displayer.input("message")
|
||||
mock_input.assert_called_with("message")
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util.dialog.Dialog.yesno")
|
||||
def test_yesno(self, mock_yesno):
|
||||
mock_yesno.return_value = display_util.OK
|
||||
|
||||
self.assertTrue(self.displayer.yesno("message"))
|
||||
|
||||
mock_yesno.assert_called_with(
|
||||
"message", display_util.HEIGHT, display_util.WIDTH,
|
||||
yes_label="Yes", no_label="No")
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util."
|
||||
"dialog.Dialog.checklist")
|
||||
def test_checklist(self, mock_checklist):
|
||||
self.displayer.checklist("message", self.tags)
|
||||
|
||||
choices = [
|
||||
(self.tags[0], "", False),
|
||||
(self.tags[1], "", False),
|
||||
(self.tags[2], "", False)
|
||||
]
|
||||
mock_checklist.assert_called_with(
|
||||
"message", width=display_util.WIDTH, height=display_util.HEIGHT,
|
||||
choices=choices)
|
||||
|
||||
# def test_visual(self):
|
||||
# test_visual(self.displayer, self.choices)
|
||||
|
||||
|
||||
class FileOutputDisplayTest(DisplayT):
|
||||
"""Test stdout display."""
|
||||
"""Test stdout display.
|
||||
|
||||
Most of this class has to deal with visual output. In order to test how the
|
||||
functions look to a user, uncomment the test_visual function.
|
||||
|
||||
"""
|
||||
def setUp(self):
|
||||
super(FileOutputDisplayTest, self).setUp()
|
||||
self.displayer = display_util.FileDisplay(sys.stdout)
|
||||
self.mock_stdout = mock.MagicMock()
|
||||
self.displayer = display_util.FileDisplay(self.mock_stdout)
|
||||
|
||||
def test_visual(self):
|
||||
test_visual(self.displayer, self.choices)
|
||||
def test_notification_no_pause(self):
|
||||
self.displayer.notification("message", 10, False)
|
||||
string = self.mock_stdout.write.call_args[0][0]
|
||||
|
||||
self.assertTrue("message" in string)
|
||||
|
||||
def test_notification_pause(self):
|
||||
# Attempt to mock raw_input
|
||||
with mock_raw_input(["enter"]):
|
||||
self.displayer.notification("message")
|
||||
|
||||
self.assertTrue("message" in self.mock_stdout.write.call_args[0][0])
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util."
|
||||
"FileDisplay._get_valid_int_ans")
|
||||
def test_menu(self, mock_ans):
|
||||
mock_ans.return_value = (display_util.OK, 1)
|
||||
ret = self.displayer.menu("message", self.choices)
|
||||
self.assertEqual(ret, (display_util.OK, 0))
|
||||
|
||||
def test_input_cancel(self):
|
||||
# Attempt to mock raw_input
|
||||
with mock_raw_input(["c"]):
|
||||
code, _ = self.displayer.input("message")
|
||||
|
||||
self.assertTrue(code, display_util.CANCEL)
|
||||
|
||||
def test_input_normal(self):
|
||||
with mock_raw_input(["domain.com"]):
|
||||
code, input_ = self.displayer.input("message")
|
||||
|
||||
self.assertEqual(code, display_util.OK)
|
||||
self.assertEqual(input_, "domain.com")
|
||||
|
||||
def test_yesno(self):
|
||||
with mock_raw_input(["Yes"]):
|
||||
self.assertTrue(self.displayer.yesno("message"))
|
||||
with mock_raw_input(["y"]):
|
||||
self.assertTrue(self.displayer.yesno("message"))
|
||||
with mock_raw_input(["cancel"]):
|
||||
self.assertFalse(self.displayer.yesno("message"))
|
||||
|
||||
with mock_raw_input(["a"]):
|
||||
self.assertTrue(self.displayer.yesno("msg", yes_label="Agree"))
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util.FileDisplay.input")
|
||||
def test_checklist_valid(self, mock_input):
|
||||
mock_input.return_value = (display_util.OK, "2 1")
|
||||
code, tag_list = self.displayer.checklist("msg", self.tags)
|
||||
self.assertEqual(
|
||||
(code, set(tag_list)), (display_util.OK, set(["tag1", "tag2"])))
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util.FileDisplay.input")
|
||||
def test_checklist_miss_valid(self, mock_input):
|
||||
mock_input.side_effect = [
|
||||
(display_util.OK, "10"),
|
||||
(display_util.OK, "tag1 please"),
|
||||
(display_util.OK, "1")
|
||||
]
|
||||
|
||||
ret = self.displayer.checklist("msg", self.tags)
|
||||
self.assertEqual(ret, (display_util.OK, ["tag1"]))
|
||||
|
||||
@mock.patch("letsencrypt.client.display.display_util.FileDisplay.input")
|
||||
def test_checklist_miss_quit(self, mock_input):
|
||||
mock_input.side_effect = [
|
||||
(display_util.OK, "10"),
|
||||
(display_util.CANCEL, "1")
|
||||
]
|
||||
ret = self.displayer.checklist("msg", self.tags)
|
||||
self.assertEqual(ret, (display_util.CANCEL, []))
|
||||
|
||||
def test_scrub_checklist_input_valid(self):
|
||||
indices = [
|
||||
["1"],
|
||||
["1", "2", "1"],
|
||||
["2", "3"],
|
||||
]
|
||||
exp = [
|
||||
set(["tag1"]),
|
||||
set(["tag1", "tag2"]),
|
||||
set(["tag2", "tag3"]),
|
||||
]
|
||||
for i, list_ in enumerate(indices):
|
||||
set_tags = set(
|
||||
self.displayer._scrub_checklist_input(list_, self.tags))
|
||||
self.assertEqual(set_tags, exp[i])
|
||||
|
||||
def test_scrub_checklist_input_invalid(self):
|
||||
indices = [
|
||||
["0"],
|
||||
["4"],
|
||||
["tag1"],
|
||||
["1", "tag1"],
|
||||
["2", "o"]
|
||||
]
|
||||
for list_ in indices:
|
||||
self.assertEqual(
|
||||
self.displayer._scrub_checklist_input(list_, self.tags), [])
|
||||
|
||||
def test_print_menu(self):
|
||||
# This is purely cosmetic... just make sure there aren't any exceptions
|
||||
self.displayer._print_menu("msg", self.choices)
|
||||
self.displayer._print_menu("msg", self.tags)
|
||||
|
||||
def test_wrap_lines(self):
|
||||
msg = ("This is just a weak test\n"
|
||||
"This function is only meant to be for easy viewing\n"
|
||||
"Test a really really really really really really really really "
|
||||
"really really really really really long line...")
|
||||
text = self.displayer._wrap_lines(msg)
|
||||
|
||||
self.assertEqual(text.count(os.linesep), 3)
|
||||
|
||||
def test_get_valid_int_ans_valid(self):
|
||||
with mock_raw_input(["1"]):
|
||||
self.assertEqual(
|
||||
self.displayer._get_valid_int_ans(1), (display_util.OK, 1))
|
||||
ans = "2"
|
||||
with mock_raw_input([ans]):
|
||||
self.assertEqual(
|
||||
self.displayer._get_valid_int_ans(3),
|
||||
(display_util.OK, int(ans)))
|
||||
|
||||
def test_get_valid_int_ans_invalid(self):
|
||||
answers = [
|
||||
["0", "c"],
|
||||
["4", "one", "C"],
|
||||
["c"],
|
||||
]
|
||||
for ans in answers:
|
||||
with mock_raw_input(ans):
|
||||
self.assertEqual(
|
||||
self.displayer._get_valid_int_ans(3),
|
||||
(display_util.CANCEL, -1))
|
||||
|
||||
# def test_visual(self):
|
||||
# self.displayer = display_util.FileDisplay(sys.stdout)
|
||||
# test_visual(self.displayer, self.choices)
|
||||
|
||||
|
||||
class SeparateListInputTest(unittest.TestCase):
|
||||
|
|
@ -101,5 +323,15 @@ class PlaceParensTest(unittest.TestCase):
|
|||
self.assertEqual("(L)abel", ret)
|
||||
|
||||
|
||||
# https://stackoverflow.com/a/25275926
|
||||
@contextlib.contextmanager
|
||||
def mock_raw_input(values):
|
||||
func = mock.MagicMock(side_effect=values)
|
||||
original_raw_input = __builtins__.raw_input
|
||||
__builtins__.raw_input = func
|
||||
yield
|
||||
__builtins__.raw_input = original_raw_input
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue