def setUp(self):
        """Install a resource instance sufficient for testing common
        things with subcommands.
        """
        class BasicResource(models.Resource):
            endpoint = '/basic/'
            name = models.Field(unique=True)

        self.resource = BasicResource()
        self.command = ResSubcommand(self.resource)
    def test_basic_launch_with_echo(self):
        """Establish that we are able to run an ad hoc command and also
        print that to the command line without errors.
        """
        with client.test_mode as t:
            t.register_json('/ad_hoc_commands/42/', {'id': 42}, method='GET')
            t.register_json('/',
                            {'ad_hoc_commands': '/api/v1/ad_hoc_commands/'},
                            method='GET')
            t.register_json('/ad_hoc_commands/', {
                'changed': True,
                'id': 42,
                'inventory': 'foobar',
                'credential': 2,
                'name': 'ping',
                'created': 1234,
                'elapsed': 2352,
                'status': 'successful',
                'module_name': 'command',
                'limit': '',
            },
                            method='POST')
            result = self.res.launch(inventory="foobar", machine_credential=2)
            self.assertEqual(result['changed'], True)
            self.assertEqual(result['id'], 42)

            f = ResSubcommand(self.res)._echo_method(self.res.launch)
            with mock.patch.object(click, 'secho'):
                with settings.runtime_values(format='human'):
                    f(inventory="foobar", machine_credential=2)
Пример #3
0
 def setUp(self):
     """Install a resource instance sufficient for testing common
     things with subcommands.
     """
     class BasicResource(models.Resource):
         endpoint = '/basic/'
         name = models.Field(unique=True)
     self.resource = BasicResource()
     self.command = ResSubcommand(self.resource)
Пример #4
0
    def lookup_stdout(self,
                      pk=None,
                      start_line=None,
                      end_line=None,
                      full=True):
        """
        Internal method that lies to our `monitor` method by returning
        a scorecard for the workflow job where the standard out
        would have been expected.
        """
        uj_res = get_resource('unified_job')
        # Filters
        #  - limit search to jobs spawned as part of this workflow job
        #  - order in the order in which they should add to the list
        #  - only include final job states
        query_params = (('unified_job_node__workflow_job', pk),
                        ('order_by', 'finished'), ('status__in',
                                                   'successful,failed,error'))
        jobs_list = uj_res.list(all_pages=True, query=query_params)
        if jobs_list['count'] == 0:
            return ''

        return_content = ResSubcommand(uj_res)._format_human(jobs_list)
        lines = return_content.split('\n')
        if not full:
            lines = lines[:-1]

        N = len(lines)
        start_range = start_line
        if start_line is None:
            start_range = 0
        elif start_line > N:
            start_range = N

        end_range = end_line
        if end_line is None or end_line > N:
            end_range = N

        lines = lines[start_range:end_range]
        return_content = '\n'.join(lines)
        if len(lines) > 0:
            return_content += '\n'

        return return_content
Пример #5
0
    def lookup_stdout(self, pk=None, start_line=None, end_line=None,
                      full=True):
        """
        Internal method that lies to our `monitor` method by returning
        a scorecard for the workflow job where the standard out
        would have been expected.
        """
        uj_res = get_resource('unified_job')
        # Filters
        #  - limit search to jobs spawned as part of this workflow job
        #  - order in the order in which they should add to the list
        #  - only include final job states
        query_params = (('unified_job_node__workflow_job', pk),
                        ('order_by', 'finished'),
                        ('status__in', 'successful,failed,error'))
        jobs_list = uj_res.list(all_pages=True, query=query_params)
        if jobs_list['count'] == 0:
            return ''

        return_content = ResSubcommand(uj_res)._format_human(jobs_list)
        lines = return_content.split('\n')
        if not full:
            lines = lines[:-1]

        N = len(lines)
        start_range = start_line
        if start_line is None:
            start_range = 0
        elif start_line > N:
            start_range = N

        end_range = end_line
        if end_line is None or end_line > N:
            end_range = N

        lines = lines[start_range:end_range]
        return_content = '\n'.join(lines)
        if len(lines) > 0:
            return_content += '\n'

        return return_content
    def test_docstring_replacement_an(self):
        """Establish that for resources with names beginning with vowels,
        that the automatic docstring replacement is gramatically correct.
        """

        # Create a resource with an approriate name.
        class Oreo(models.Resource):
            resource_name = 'Oreo cookie'  # COOOOOOKIES!!!!
            endpoint = '/oreo/'

        # Get the Oreo resource's create method.
        create = ResSubcommand(Oreo()).get_command(None, 'create')
        self.assertIn('Create an Oreo cookie', create.help)
    def test_docstring_replacement_y(self):
        """Establish that for resources with names ending in y, that plural
        replacement is correct.
        """

        # Create a resource with an approriate name.
        class Oreo(models.Resource):
            resource_name = 'telephony'
            endpoint = '/telephonies/'

        # Get the Oreo resource's create method.
        create = ResSubcommand(Oreo()).get_command(None, 'list')
        self.assertIn('list of telephonies', create.help)
Пример #8
0
    def test_basic_launch_with_echo(self):
        """Establish that we are able to create a job and echo the output
        to the command line without it breaking.
        """
        with client.test_mode as t:
            standard_registration(t)
            result = self.res.launch(1)
            self.assertDictContainsSubset({'changed': True, 'id': 42}, result)

            f = ResSubcommand(self.res)._echo_method(self.res.launch)
            with mock.patch.object(click, 'secho'):
                with settings.runtime_values(format='human'):
                    f(job_template=1)
    def test_field_help_text_has_prefix(self):
        """Establish that resource field help text is properly prefixed.
        """
        class FieldHelpTextResource(models.Resource):
            endpoint = '/foobar/'

            option_name = models.Field('internal_name',
                                       help_text='foobar',
                                       required=False)

        cmd = ResSubcommand(FieldHelpTextResource()).get_command(None, 'get')

        opt = cmd.params[0]
        self.assertEqual(opt.help, '[FIELD]foobar')
Пример #10
0
    def test_field_help_text_has_suffix_for_structured_input(self):
        """Establish that resource field help text is properly suffixed if field type is StructuredInput.
        """
        class FieldHelpTextResource(models.Resource):
            endpoint = '/foobar/'

            option_name = models.Field('internal_name',
                                       type=StructuredInput(),
                                       help_text='foobar',
                                       required=False)

        cmd = ResSubcommand(FieldHelpTextResource()).get_command(None, 'get')

        opt = cmd.params[0]
        self.assertEqual(
            opt.help.endswith(' Use @ to get JSON or YAML from a file.'), True)
    def test_command_with_pk(self):
        """Establish that the `get_command` method appropriately adds a
        primary key argument if the method has a "pk" positional argument.
        """

        # Create a resource with an appropriate command.
        class PKResource(models.BaseResource):
            endpoint = '/pkr/'

            @resources.command
            def my_method(self, pk):
                pass

        # Get the command version of my_method.
        my_method = ResSubcommand(PKResource()).get_command(None, 'my_method')

        # Establish that the `my_method` command does, in fact, have a PK
        # click argument attached.
        self.assertEqual(my_method.params[-1].name, 'pk')
    def test_fields_not_options(self):
        """Establish that a field which is not an option is not made into
        an option for commands.
        """

        # Create a resource with a field that is an option and another
        # field that isn't.
        class NoOptionResource(models.Resource):
            endpoint = '/nor/'

            yes = models.Field()
            no = models.Field(is_option=False)

        # Make the resource into a command, and get a reasonably-arbitrary
        # subcommand.
        cmd = ResSubcommand(NoOptionResource()).get_command(None, 'list')

        # Establish that "yes" is an option on the command and "no" is not.
        self.assertTrue(any([o.name == 'yes' for o in cmd.params]))
        self.assertFalse(any([o.name == 'no' for o in cmd.params]))
    def test_field_explicit_key(self):
        """Establish that if a field is given an explicit key, that they
        key is used for the field name instead of the implicit name.
        """

        # Create a resource with a field that has an explicit key.
        class ExplicitKeyResource(models.Resource):
            endpoint = '/ekr/'

            option_name = models.Field('internal_name')

        # Make the resource into a command, and get a reasonably-arbitrary
        # subcommand.
        cmd = ResSubcommand(ExplicitKeyResource()).get_command(None, 'get')

        # Establish that the field has an option of --option-name, and
        # a name of internal_name.
        opt = cmd.params[0]
        self.assertEqual(opt.name, 'internal_name')
        self.assertEqual(opt.opts, ['--option-name'])
Пример #14
0
    def get_command(self, ctx, name):
        """Given a command identified by its name, import the appropriate
        module and return the decorated command.

        Resources are automatically commands, but if both a resource and
        a command are defined, the command takes precedence.
        """
        # First, attempt to get a basic command from `tower_cli.api.misc`.
        if name in misc.__all__:
            return getattr(misc, name)

        # No command was found; try to get a resource.
        try:
            resource = tower_cli.get_resource(name)
            return ResSubcommand(resource)
        except ImportError:
            pass

        # Okay, we weren't able to find a command.
        secho('No such command: %s.' % name, fg='red', bold=True)
        sys.exit(2)
    def test_job_template_create_with_echo(self):
        """Establish that a job template can be created
        """
        with client.test_mode as t:
            endpoint = '/job_templates/'
            t.register_json(endpoint, {
                'count': 0,
                'results': [],
                'next': None,
                'previous': None
            },
                            method='GET')
            t.register_json(endpoint, {
                'changed': True,
                'id': 42,
                'name': 'bar',
                'inventory': 1,
                'project': 1,
                'playbook': 'foobar.yml',
                'credential': 1
            },
                            method='POST')
            self.res.create(name='bar',
                            job_type='run',
                            inventory=1,
                            project=1,
                            playbook='foobar.yml',
                            credential=1)

            f = ResSubcommand(self.res)._echo_method(self.res.create)
            with mock.patch.object(click, 'secho'):
                with settings.runtime_values(format='human'):
                    f(name='bar',
                      job_type='run',
                      inventory=1,
                      project=1,
                      playbook='foobar.yml',
                      credential=1)
    def test_use_fields_as_options_false(self):
        """Establish that the `use_fields_as_options` attribute is honored
        if set to False.
        """

        # Create a resource with a command that doesn't expect its
        # fields to become options.
        class NoOptResource(models.BaseResource):
            endpoint = '/nor/'

            f1 = models.Field()
            f2 = models.Field()

            @resources.command(use_fields_as_options=False)
            def noopt(self):
                pass

        # Make the resource into a command, and get the noopt subcommand.
        noopt = ResSubcommand(NoOptResource()).get_command(None, 'noopt')

        # Establish that the noopt command does NOT have fields as options.
        self.assertFalse(any([o.name == 'f1' for o in noopt.params]))
        self.assertFalse(any([o.name == 'f2' for o in noopt.params]))
Пример #17
0
 def test_delete_method_is_disabled(self):
     """The create method is properly disabled."""
     self.assertEqual(ResSubcommand(self.res).get_command(None, 'delete'),
                      None)
 def test_removed_methods(self):
     """Test that None is returned from removed methods."""
     self.assertEqual(
         ResSubcommand(self.res).get_command(None, 'delete'), None)
Пример #19
0
 def test_delete_method_is_disabled(self):
     """Establish that delete method of a label is properly disabled.
     """
     self.assertEqual(
         ResSubcommand(self.res).get_command(None, 'delete'), None)
class SubcommandTests(unittest.TestCase):
    """A set of tests for establishing that the Subcommand class created
    on the basis of a Reosurce class works in the way we expect.
    """
    def setUp(self):
        """Install a resource instance sufficient for testing common
        things with subcommands.
        """
        class BasicResource(models.Resource):
            endpoint = '/basic/'
            name = models.Field(unique=True)

        self.resource = BasicResource()
        self.command = ResSubcommand(self.resource)

    def test_command_instance(self):
        """Establish that the command based on a resource is, in fact, a
        click MultiCommand.
        """
        # Assert that it is a click command, and that it has the commands
        # available on the resource.
        self.assertIsInstance(self.command, click.MultiCommand)

    def test_list_commands(self):
        """Establish that the `list_commands` method for the command
        corresponds to the commands available on the resource.
        """
        self.assertEqual(set(self.resource.commands),
                         set(self.command.list_commands(None)))

    def test_get_command(self):
        """Establish that the `get_command` method returns the appropriate
        resource method wrapped as a click command.
        """
        list_command = self.command.get_command(None, 'list')

        # Ensure that this is a click command.
        self.assertIsInstance(list_command, click.core.Command)

        # Ensure that this command has an option corresponding to the
        # "name" unique field.
        self.assertEqual(list_command.params[0].name, 'name')
        self.assertIn('--name', list_command.params[0].opts)

    def test_get_command_error(self):
        """Establish that if `get_command` is called against a command that
        does not actually exist on the resource, that null value is returned.
        """
        self.assertEqual(self.command.get_command(None, 'bogus'), None)

    def test_command_with_pk(self):
        """Establish that the `get_command` method appropriately adds a
        primary key argument if the method has a "pk" positional argument.
        """

        # Create a resource with an appropriate command.
        class PKResource(models.BaseResource):
            endpoint = '/pkr/'

            @resources.command
            def my_method(self, pk):
                pass

        # Get the command version of my_method.
        my_method = ResSubcommand(PKResource()).get_command(None, 'my_method')

        # Establish that the `my_method` command does, in fact, have a PK
        # click argument attached.
        self.assertEqual(my_method.params[-1].name, 'pk')

    def test_use_fields_as_options_false(self):
        """Establish that the `use_fields_as_options` attribute is honored
        if set to False.
        """

        # Create a resource with a command that doesn't expect its
        # fields to become options.
        class NoOptResource(models.BaseResource):
            endpoint = '/nor/'

            f1 = models.Field()
            f2 = models.Field()

            @resources.command(use_fields_as_options=False)
            def noopt(self):
                pass

        # Make the resource into a command, and get the noopt subcommand.
        noopt = ResSubcommand(NoOptResource()).get_command(None, 'noopt')

        # Establish that the noopt command does NOT have fields as options.
        self.assertFalse(any([o.name == 'f1' for o in noopt.params]))
        self.assertFalse(any([o.name == 'f2' for o in noopt.params]))

    def test_use_fields_as_options_enumerated(self):
        """Establish that the `use_fields_as_options` attribute is honored
        if set to a tuple containing a subset of fields.
        """

        # Create a resource with a command that doesn't expect its
        # fields to become options.
        class NoOptResource(models.BaseResource):
            endpoint = '/nor/'

            f1 = models.Field()
            f2 = models.Field()

            @resources.command(use_fields_as_options=('f2', ))
            def noopt(self):
                pass

        # Make the resource into a command, and get the noopt subcommand.
        noopt = ResSubcommand(NoOptResource()).get_command(None, 'noopt')

        # Establish that the noopt command does NOT have fields as options.
        self.assertFalse(any([o.name == 'f1' for o in noopt.params]))
        self.assertTrue(any([o.name == 'f2' for o in noopt.params]))

    def test_fields_not_options(self):
        """Establish that a field which is not an option is not made into
        an option for commands.
        """

        # Create a resource with a field that is an option and another
        # field that isn't.
        class NoOptionResource(models.Resource):
            endpoint = '/nor/'

            yes = models.Field()
            no = models.Field(is_option=False)

        # Make the resource into a command, and get a reasonably-arbitrary
        # subcommand.
        cmd = ResSubcommand(NoOptionResource()).get_command(None, 'list')

        # Establish that "yes" is an option on the command and "no" is not.
        self.assertTrue(any([o.name == 'yes' for o in cmd.params]))
        self.assertFalse(any([o.name == 'no' for o in cmd.params]))

    def test_field_explicit_key(self):
        """Establish that if a field is given an explicit key, that they
        key is used for the field name instead of the implicit name.
        """

        # Create a resource with a field that has an explicit key.
        class ExplicitKeyResource(models.Resource):
            endpoint = '/ekr/'

            option_name = models.Field('internal_name')

        # Make the resource into a command, and get a reasonably-arbitrary
        # subcommand.
        cmd = ResSubcommand(ExplicitKeyResource()).get_command(None, 'get')

        # Establish that the field has an option of --option-name, and
        # a name of internal_name.
        opt = cmd.params[0]
        self.assertEqual(opt.name, 'internal_name')
        self.assertEqual(opt.opts, ['--option-name'])

    def test_field_help_text_has_prefix(self):
        """Establish that resource field help text is properly prefixed.
        """
        class FieldHelpTextResource(models.Resource):
            endpoint = '/foobar/'

            option_name = models.Field('internal_name',
                                       help_text='foobar',
                                       required=False)

        cmd = ResSubcommand(FieldHelpTextResource()).get_command(None, 'get')

        opt = cmd.params[0]
        self.assertEqual(opt.help, '[FIELD]foobar')

    def test_docstring_replacement_an(self):
        """Establish that for resources with names beginning with vowels,
        that the automatic docstring replacement is gramatically correct.
        """

        # Create a resource with an approriate name.
        class Oreo(models.Resource):
            resource_name = 'Oreo cookie'  # COOOOOOKIES!!!!
            endpoint = '/oreo/'

        # Get the Oreo resource's create method.
        create = ResSubcommand(Oreo()).get_command(None, 'create')
        self.assertIn('Create an Oreo cookie', create.help)

    def test_docstring_replacement_y(self):
        """Establish that for resources with names ending in y, that plural
        replacement is correct.
        """

        # Create a resource with an approriate name.
        class Oreo(models.Resource):
            resource_name = 'telephony'
            endpoint = '/telephonies/'

        # Get the Oreo resource's create method.
        create = ResSubcommand(Oreo()).get_command(None, 'list')
        self.assertIn('list of telephonies', create.help)

    def test_echo_method(self):
        """Establish that the _echo_method subcommand class works in the
        way we expect.
        """
        func = self.command._echo_method(lambda: {'foo': 'bar'})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='json'):
                func()
            secho.assert_called_once_with(json.dumps({'foo': 'bar'}, indent=2))

    def test_echo_method_changed_false(self):
        """Establish that the _echo_method subcommand decorator works
        in the way we expect if we get an unchanged designation.
        """
        func = self.command._echo_method(lambda: {'changed': False})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='json', color=True):
                func()
            answer = json.dumps({'changed': False}, indent=2)
            secho.assert_called_once_with(answer, fg='green')

    def test_echo_method_changed_true(self):
        """Establish that the _echo_method subcommand decorator works
        in the way we expect if we get an changed designation.
        """
        func = self.command._echo_method(lambda: {'changed': True})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='json', color=True):
                func()
            answer = json.dumps({'changed': True}, indent=2)
            secho.assert_called_once_with(answer, fg='yellow')

    def test_echo_method_yaml_formatted(self):
        """Establish that the `_echo_method` properly returns YAML formatting
        when it gets back a list of objects.
        """
        func = self.command._echo_method(lambda: {'foo': 'bar'})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='yaml'):
                func()
            secho.assert_called_once_with(
                yaml.safe_dump({'foo': 'bar'},
                               indent=2,
                               allow_unicode=True,
                               default_flow_style=False))

    def test_echo_method_human_formatted(self):
        """Establish that the `_echo_method` properly returns human formatting
        when it gets back a list of objects.
        """
        func = self.command._echo_method(
            lambda: {
                'results': [
                    {
                        'id': 1,
                        'name': 'Durham, NC'
                    },
                    {
                        'id': 2,
                        'name': 'Austin, TX'
                    },
                ]
            })
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('1 Durham, NC', output)
        self.assertIn('2 Austin, TX', output)

    def test_echo_method_human_formatted_changed(self):
        """Establish that if there is a change and no id is returned,
        we print a generic OK message.
        """
        func = self.command._echo_method(lambda: {'changed': False})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertEqual(output, 'OK. (changed: false)')

    def test_echo_method_human_formatted_no_records(self):
        """Establish that if there are no records sent to the human formatter,
        that it prints a terse message to that effect.
        """
        func = self.command._echo_method(lambda: {'results': []})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertEqual(output, 'No records found.')

    def test_echo_method_human_formatted_single_result(self):
        """Establish that a single result sent to the human formatter
        shows a table with a single row as expected.
        """
        f = self.command._echo_method(lambda: {'id': 1, 'name': 'Durham, NC'})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                f()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('1 Durham, NC', output)

    def test_echo_method_human_boolean_formatting(self):
        """Establish that booleans are formatted right-aligned, lower-cased
        in human output.
        """
        func = self.command._echo_method(
            lambda: {
                'results': [
                    {
                        'id': 1,
                        'name': 'Durham, NC'
                    },
                    {
                        'id': 2,
                        'name': True
                    },
                ]
            })
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('1 Durham, NC', output)
        self.assertIn('2       true', output)

    def test_echo_method_human_pagination(self):
        """Establish that pagination works in human formatting, and it
        prints the way we expect.
        """
        func = self.command._echo_method(
            lambda: {
                'results': [
                    {
                        'id': 1,
                        'name': 'Durham, NC'
                    },
                    {
                        'id': 2,
                        'name': True
                    },
                ],
                'next':
                3,
                'count':
                10,
                'previous':
                1
            })
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('(Page 2 of 5.)', output)

    def test_echo_method_human_pagination_last_page(self):
        """Establish that pagination works in human formatting, and it
        prints the way we expect on the final page..
        """
        func = self.command._echo_method(
            lambda: {
                'results': [
                    {
                        'id': 1,
                        'name': 'Durham, NC'
                    },
                ],
                'next': None,
                'count': 3,
                'previous': 1
            })
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('(Page 2 of 2.)', output)

    def test_echo_method_human_custom_output(self):
        """Establish that a custom dictionary with no ID is made into a
        table and printed as expected.
        """
        func = self.command._echo_method(lambda: {
            'foo': 'bar',
            'spam': 'eggs'
        })
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('foo', output)
        self.assertIn('spam', output)
        self.assertIn('bar', output)
        self.assertIn('eggs', output)

    def test_echo_id(self):
        func = self.command._echo_method(lambda: {'id': 5})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='id'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertEqual('5', output)
Пример #21
0
class SubcommandTests(unittest.TestCase):
    """A set of tests for establishing that the Subcommand class created
    on the basis of a Reosurce class works in the way we expect.
    """
    def setUp(self):
        """Install a resource instance sufficient for testing common
        things with subcommands.
        """
        class BasicResource(models.Resource):
            endpoint = '/basic/'
            name = models.Field(unique=True)
        self.resource = BasicResource()
        self.command = ResSubcommand(self.resource)

    def test_command_instance(self):
        """Establish that the command based on a resource is, in fact, a
        click MultiCommand.
        """
        # Assert that it is a click command, and that it has the commands
        # available on the resource.
        self.assertIsInstance(self.command, click.MultiCommand)

    def test_list_commands(self):
        """Establish that the `list_commands` method for the command
        corresponds to the commands available on the resource.
        """
        self.assertEqual(set(self.resource.commands),
                         set(self.command.list_commands(None)))

    def test_get_command(self):
        """Establish that the `get_command` method returns the appropriate
        resource method wrapped as a click command.
        """
        list_command = self.command.get_command(None, 'list')

        # Ensure that this is a click command.
        self.assertIsInstance(list_command, click.core.Command)

        # Ensure that this command has an option corresponding to the
        # "name" unique field.
        self.assertEqual(list_command.params[0].name, 'name')
        self.assertIn('--name', list_command.params[0].opts)

    def test_get_command_error(self):
        """Establish that if `get_command` is called against a command that
        does not actually exist on the resource, that null value is returned.
        """
        self.assertEqual(self.command.get_command(None, 'bogus'), None)

    def test_command_with_pk(self):
        """Establish that the `get_command` method appropriately adds a
        primary key argument if the method has a "pk" positional argument.
        """
        # Create a resource with an appropriate command.
        class PKResource(models.BaseResource):
            endpoint = '/pkr/'

            @resources.command
            def my_method(self, pk):
                pass

        # Get the command version of my_method.
        my_method = ResSubcommand(PKResource()).get_command(None, 'my_method')

        # Establish that the `my_method` command does, in fact, have a PK
        # click argument attached.
        self.assertEqual(my_method.params[-1].name, 'pk')

    def test_use_fields_as_options_false(self):
        """Establish that the `use_fields_as_options` attribute is honored
        if set to False.
        """
        # Create a resource with a command that doesn't expect its
        # fields to become options.
        class NoOptResource(models.BaseResource):
            endpoint = '/nor/'

            f1 = models.Field()
            f2 = models.Field()

            @resources.command(use_fields_as_options=False)
            def noopt(self):
                pass

        # Make the resource into a command, and get the noopt subcommand.
        noopt = ResSubcommand(NoOptResource()).get_command(None, 'noopt')

        # Establish that the noopt command does NOT have fields as options.
        self.assertFalse(any([o.name == 'f1' for o in noopt.params]))
        self.assertFalse(any([o.name == 'f2' for o in noopt.params]))

    def test_use_fields_as_options_enumerated(self):
        """Establish that the `use_fields_as_options` attribute is honored
        if set to a tuple containing a subset of fields.
        """
        # Create a resource with a command that doesn't expect its
        # fields to become options.
        class NoOptResource(models.BaseResource):
            endpoint = '/nor/'

            f1 = models.Field()
            f2 = models.Field()

            @resources.command(use_fields_as_options=('f2',))
            def noopt(self):
                pass

        # Make the resource into a command, and get the noopt subcommand.
        noopt = ResSubcommand(NoOptResource()).get_command(None, 'noopt')

        # Establish that the noopt command does NOT have fields as options.
        self.assertFalse(any([o.name == 'f1' for o in noopt.params]))
        self.assertTrue(any([o.name == 'f2' for o in noopt.params]))

    def test_fields_not_options(self):
        """Establish that a field which is not an option is not made into
        an option for commands.
        """
        # Create a resource with a field that is an option and another
        # field that isn't.
        class NoOptionResource(models.Resource):
            endpoint = '/nor/'

            yes = models.Field()
            no = models.Field(is_option=False)

        # Make the resource into a command, and get a reasonably-arbitrary
        # subcommand.
        cmd = ResSubcommand(NoOptionResource()).get_command(None, 'list')

        # Establish that "yes" is an option on the command and "no" is not.
        self.assertTrue(any([o.name == 'yes' for o in cmd.params]))
        self.assertFalse(any([o.name == 'no' for o in cmd.params]))

    def test_field_explicit_key(self):
        """Establish that if a field is given an explicit key, that they
        key is used for the field name instead of the implicit name.
        """
        # Create a resource with a field that has an explicit key.
        class ExplicitKeyResource(models.Resource):
            endpoint = '/ekr/'

            option_name = models.Field('internal_name')

        # Make the resource into a command, and get a reasonably-arbitrary
        # subcommand.
        cmd = ResSubcommand(ExplicitKeyResource()).get_command(None, 'get')

        # Establish that the field has an option of --option-name, and
        # a name of internal_name.
        opt = cmd.params[0]
        self.assertEqual(opt.name, 'internal_name')
        self.assertEqual(opt.opts, ['--option-name'])

    def test_field_help_text_has_prefix(self):
        """Establish that resource field help text is properly prefixed.
        """
        class FieldHelpTextResource(models.Resource):
            endpoint = '/foobar/'

            option_name = models.Field('internal_name', help_text='foobar', required=False)

        cmd = ResSubcommand(FieldHelpTextResource()).get_command(None, 'get')

        opt = cmd.params[0]
        self.assertEqual(opt.help, '[FIELD]foobar')

    def test_field_help_text_has_suffix_for_structured_input(self):
        """Establish that resource field help text is properly suffixed if field type is StructuredInput.
        """
        class FieldHelpTextResource(models.Resource):
            endpoint = '/foobar/'

            option_name = models.Field('internal_name', type=StructuredInput(), help_text='foobar', required=False)

        cmd = ResSubcommand(FieldHelpTextResource()).get_command(None, 'get')

        opt = cmd.params[0]
        self.assertEqual(opt.help.endswith(' Use @ to get JSON or YAML from a file.'), True)

    def test_docstring_replacement_an(self):
        """Establish that for resources with names beginning with vowels,
        that the automatic docstring replacement is grammatically correct.
        """
        # Create a resource with an approriate name.
        class Oreo(models.Resource):
            resource_name = 'Oreo cookie'   # COOOOOOKIES!!!!
            endpoint = '/oreo/'

        # Get the Oreo resource's create method.
        create = ResSubcommand(Oreo()).get_command(None, 'create')
        self.assertIn('Create an Oreo cookie', create.help)

    def test_docstring_replacement_y(self):
        """Establish that for resources with names ending in y, that plural
        replacement is correct.
        """
        # Create a resource with an approriate name.
        class Oreo(models.Resource):
            resource_name = 'telephony'
            endpoint = '/telephonies/'

        # Get the Oreo resource's create method.
        create = ResSubcommand(Oreo()).get_command(None, 'list')
        self.assertIn('list of telephonies', create.help)

    def test_echo_method(self):
        """Establish that the _echo_method subcommand class works in the
        way we expect.
        """
        func = self.command._echo_method(lambda: {'foo': 'bar'})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='json'):
                func()
            secho.assert_called_once_with(json.dumps({'foo': 'bar'}, indent=2))

    def test_echo_method_format_freezer(self):
        """Establish that the _echo_method subcommand class respects format_freezer
        attribute of inner method.
        """
        def inner_func():
            return {'foo': 'bar'}
        inner_func.format_freezer = 'json'
        func = self.command._echo_method(inner_func)
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            secho.assert_called_once_with(json.dumps({'foo': 'bar'}, indent=2))

    def test_echo_method_changed_false(self):
        """Establish that the _echo_method subcommand decorator works
        in the way we expect if we get an unchanged designation.
        """
        func = self.command._echo_method(lambda: {'changed': False})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='json', color=True):
                func()
            answer = json.dumps({'changed': False}, indent=2)
            secho.assert_called_once_with(answer, fg='green')

    def test_echo_method_changed_true(self):
        """Establish that the _echo_method subcommand decorator works
        in the way we expect if we get an changed designation.
        """
        func = self.command._echo_method(lambda: {'changed': True})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='json', color=True):
                func()
            answer = json.dumps({'changed': True}, indent=2)
            secho.assert_called_once_with(answer, fg='yellow')

    def test_echo_method_yaml_formatted(self):
        """Establish that the `_echo_method` properly returns YAML formatting
        when it gets back a list of objects.
        """
        func = self.command._echo_method(lambda: {'foo': 'bar'})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='yaml'):
                func()
            secho.assert_called_once_with(yaml.safe_dump({'foo': 'bar'},
                                          indent=2,
                                          allow_unicode=True,
                                          default_flow_style=False))

    def test_echo_method_human_formatted(self):
        """Establish that the `_echo_method` properly returns human formatting
        when it gets back a list of objects.
        """
        func = self.command._echo_method(lambda: {'results': [
            {'id': 1, 'name': 'Durham, NC'},
            {'id': 2, 'name': 'Austin, TX'},
        ]})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('1 Durham, NC', output)
        self.assertIn('2 Austin, TX', output)

    def test_unicode_human_formatting(self):
        value = u'unicode ❤ ☀ ☆ ☂'
        data = {
            'count': 1,
            'results': [
                {'id': 1, 'name': 'ascii'},
                {
                    'id': 42,
                    'name': ''
                }
            ]
        }
        data['results'][1]['name'] = value
        output = self.command._format_human(data)
        assert value in output

    def test_echo_method_human_formatted_changed(self):
        """Establish that if there is a change and no id is returned,
        we print a generic OK message.
        """
        func = self.command._echo_method(lambda: {'changed': False})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertEqual(output, 'OK. (changed: false)')

    def test_echo_method_human_formatted_no_records(self):
        """Establish that if there are no records sent to the human formatter,
        that it prints a terse message to that effect.
        """
        func = self.command._echo_method(lambda: {'results': []})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertEqual(output, 'No records found.')

    def test_echo_method_human_formatted_single_result(self):
        """Establish that a single result sent to the human formatter
        shows a table with a single row as expected.
        """
        f = self.command._echo_method(lambda: {'id': 1, 'name': 'Durham, NC'})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                f()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('1 Durham, NC', output)

    def test_echo_method_human_boolean_formatting(self):
        """Establish that booleans are formatted right-aligned, lower-cased
        in human output.
        """
        func = self.command._echo_method(lambda: {'results': [
            {'id': 1, 'name': 'Durham, NC'},
            {'id': 2, 'name': True},
        ]})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('1 Durham, NC', output)
        self.assertIn('2       true', output)

    def test_echo_method_human_pagination(self):
        """Establish that pagination works in human formatting, and it
        prints the way we expect.
        """
        func = self.command._echo_method(lambda: {'results': [
            {'id': 1, 'name': 'Durham, NC'},
            {'id': 2, 'name': True},
        ], 'next': 3, 'count': 10, 'previous': 1})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('(Page 2 of 5.)', output)

    def test_echo_method_human_pagination_last_page(self):
        """Establish that pagination works in human formatting, and it
        prints the way we expect on the final page..
        """
        func = self.command._echo_method(lambda: {'results': [
            {'id': 1, 'name': 'Durham, NC'},
        ], 'next': None, 'count': 3, 'previous': 1})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('(Page 2 of 2.)', output)

    def test_echo_method_human_custom_output(self):
        """Establish that a custom dictionary with no ID is made into a
        table and printed as expected.
        """
        func = self.command._echo_method(lambda:
                                         {'foo': 'bar', 'spam': 'eggs'})
        with mock.patch.object(click, 'secho') as secho:
            with settings.runtime_values(format='human'):
                func()
            output = secho.mock_calls[-1][1][0]
        self.assertIn('foo', output)
        self.assertIn('spam', output)
        self.assertIn('bar', output)
        self.assertIn('eggs', output)

    def test_echo_id(self):
        for input_format in [{'id': 5}, {'count': 1, 'results': [{'id': 5}]}]:
            func = self.command._echo_method(lambda: input_format)
            with mock.patch.object(click, 'secho') as secho:
                with settings.runtime_values(format='id'):
                    func()
                output = secho.mock_calls[-1][1][0]
            self.assertEqual('5', output)