Ian Cordasco d1a88bad18 Add --format to the client shell
Add a new way of formatting our output in a consistent way. This turns
print_list and print_dict into a formatter that has the same API as any
other formatter and allows users to create their own formatters and plug
them into cratonclient.

This includes tests for the base level formatter and our two default
formatters as well as some refactoring to allow users to specify their
own --format.

At the moment, however, the subcommand shells do *not* use the pluggable
formatter decided by the user. That change and all of the downstream
effects it has on testing is going to be *very* significant and deserves
its own commit as this one is large enough.

Change-Id: I6649ebce57d5ddf2d4aeb689e77e3c17ef3a2e97
2017-02-27 14:31:59 -06:00

172 lines
6.2 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Tests for the cratonclient.shell.main module."""
import argparse
import sys
import mock
import six
import cratonclient
from cratonclient.shell import main
from cratonclient.tests import base
class TestEntryPoint(base.TestCase):
"""Tests for the craton shell entry-point."""
def setUp(self):
"""Patch out the CratonShell class."""
super(TestEntryPoint, self).setUp()
self.class_mock = mock.patch('cratonclient.shell.main.CratonShell')
self.craton_shell = self.class_mock.start()
self.addCleanup(self.class_mock.stop)
self.print_mock = mock.patch('cratonclient.shell.main.print')
self.print_func = self.print_mock.start()
self.addCleanup(self.print_mock.stop)
self.sys_exit_mock = mock.patch('sys.exit')
self.sys_exit = self.sys_exit_mock.start()
self.addCleanup(self.sys_exit_mock.stop)
def test_entry_point_creates_a_shell_instance(self):
"""Verify that our main entry-point uses CratonShell."""
CratonShell = self.craton_shell
main.main()
CratonShell.assert_called_once_with()
def test_entry_point_calls_shell_main_method(self):
"""Verify we call the main method on our CratonShell instance."""
shell_instance = mock.Mock()
self.craton_shell.return_value = shell_instance
main.main()
self.assertTrue(shell_instance.main.called)
def test_entry_point_converts_args_to_text(self):
"""Verify we call the main method with a list of text objects."""
shell_instance = mock.Mock()
self.craton_shell.return_value = shell_instance
main.main()
# NOTE(sigmavirus24): call_args is a tuple of positional arguments and
# keyword arguments, so since we pass a list positionally, we want the
# first of the positional arguments.
arglist = shell_instance.main.call_args[0][0]
self.assertTrue(
all(isinstance(arg, six.text_type) for arg in arglist)
)
def test_entry_point_handles_all_exceptions(self):
"""Verify that we handle unexpected exceptions and print a message."""
shell_instance = mock.Mock()
shell_instance.main.side_effect = ValueError
self.craton_shell.return_value = shell_instance
main.main()
self.print_func.assert_called_once_with(
"ERROR: ",
file=sys.stderr,
)
class TestCratonShell(base.TestCase):
"""Tests for the CratonShell class."""
def setUp(self):
"""Create an instance of CratonShell for each test."""
super(TestCratonShell, self).setUp()
self.shell = main.CratonShell()
def test_get_base_parser(self):
"""Verify how we construct our basic Argument Parser."""
with mock.patch('argparse.ArgumentParser') as ArgumentParser:
parser = self.shell.get_base_parser()
self.assertEqual(ArgumentParser.return_value, parser)
ArgumentParser.assert_called_once_with(
prog='craton',
description=('Main shell for parsing arguments directed toward '
'Craton.'),
epilog='See "craton help COMMAND" for help on a specific command.',
add_help=False,
formatter_class=argparse.HelpFormatter,
)
def test_get_base_parser_sets_default_options(self):
"""Verify how we construct our basic Argument Parser."""
with mock.patch('cratonclient.common.cliutils.env') as env:
env.return_value = ''
with mock.patch('argparse.ArgumentParser'):
parser = self.shell.get_base_parser()
self.assertEqual([
mock.call(
'-h', '--help', action='store_true', help=argparse.SUPPRESS,
),
mock.call(
'--version', action='version',
version=cratonclient.__version__,
),
mock.call(
'--format', default='default', choices=['default', 'json'],
help=mock.ANY,
),
mock.call(
'--craton-url', default='',
help='The base URL of the running Craton service. '
'Defaults to env[CRATON_URL].',
),
mock.call(
'--craton-version', default='',
type=int,
help='The version of the Craton API to use. '
'Defaults to env[CRATON_VERSION].'
),
mock.call(
'--os-project-id', default='',
help='The project ID used to authenticate to Craton. '
'Defaults to env[OS_PROJECT_ID].',
),
mock.call(
'--os-username', default='',
help='The username used to authenticate to Craton. '
'Defaults to env[OS_USERNAME].',
),
mock.call(
'--os-password', default='',
help='The password used to authenticate to Craton. '
'Defaults to env[OS_PASSWORD].',
),
],
parser.add_argument.call_args_list,
)
def test_get_base_parser_retrieves_environment_values(self):
"""Verify the environment variables that are requested."""
with mock.patch('cratonclient.common.cliutils.env') as env:
self.shell.get_base_parser()
self.assertEqual([
mock.call('CRATON_URL'),
mock.call('CRATON_VERSION', default=1),
mock.call('OS_PROJECT_ID'),
mock.call('OS_USERNAME'),
mock.call('OS_PASSWORD'),
],
env.call_args_list,
)