# Copyright 2013 OpenStack Foundation
# Copyright (C) 2013 Yahoo! Inc.
# All Rights Reserved.
#
#    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.
import argparse
import json
import mock
import os
import six
import tempfile
import testtools

from glanceclient.common import utils
from glanceclient import exc
from glanceclient import shell

# NOTE(geguileo): This is very nasty, but I can't find a better way to set
# command line arguments in glanceclient.v2.shell.do_image_create that are
# set by decorator utils.schema_args while preserving the spirits of the test

# Backup original decorator
original_schema_args = utils.schema_args


# Set our own decorator that calls the original but with simulated schema
def schema_args(schema_getter, omit=None):
    global original_schema_args
    # We only add the 2 arguments that are required by image-create
    my_schema_getter = lambda: {
        'properties': {
            'container_format': {
                'enum': [None, 'ami', 'ari', 'aki', 'bare', 'ovf', 'ova',
                         'docker'],
                'type': 'string',
                'description': 'Format of the container'},
            'disk_format': {
                'enum': [None, 'ami', 'ari', 'aki', 'vhd', 'vhdx', 'vmdk',
                         'raw', 'qcow2', 'vdi', 'iso', 'ploop'],
                'type': 'string',
                'description': 'Format of the disk'},
            'location': {'type': 'string'},
            'locations': {'type': 'string'},
            'copy_from': {'type': 'string'}}}
    return original_schema_args(my_schema_getter, omit)
utils.schema_args = schema_args

from glanceclient.v2 import shell as test_shell

# Return original decorator.
utils.schema_args = original_schema_args


class ShellV2Test(testtools.TestCase):
    def setUp(self):
        super(ShellV2Test, self).setUp()
        self._mock_utils()
        self.gc = self._mock_glance_client()
        self.shell = shell.OpenStackImagesShell()
        os.environ = {
            'OS_USERNAME': 'username',
            'OS_PASSWORD': 'password',
            'OS_TENANT_ID': 'tenant_id',
            'OS_TOKEN_ID': 'test',
            'OS_AUTH_URL': 'http://127.0.0.1:5000/v2.0/',
            'OS_AUTH_TOKEN': 'pass',
            'OS_IMAGE_API_VERSION': '1',
            'OS_REGION_NAME': 'test',
            'OS_IMAGE_URL': 'http://is.invalid'}
        self.shell = shell.OpenStackImagesShell()
        self.patched = mock.patch('glanceclient.common.utils.get_data_file',
                                  autospec=True, return_value=None)
        self.mock_get_data_file = self.patched.start()

    def tearDown(self):
        super(ShellV2Test, self).tearDown()
        self.patched.stop()

    def _make_args(self, args):
        # NOTE(venkatesh): this conversion from a dict to an object
        # is required because the test_shell.do_xxx(gc, args) methods
        # expects the args to be attributes of an object. If passed as
        # dict directly, it throws an AttributeError.
        class Args(object):
            def __init__(self, entries):
                self.__dict__.update(entries)

        return Args(args)

    def _mock_glance_client(self):
        my_mocked_gc = mock.Mock()
        my_mocked_gc.schemas.return_value = 'test'
        my_mocked_gc.get.return_value = {}
        return my_mocked_gc

    def _mock_utils(self):
        utils.print_list = mock.Mock()
        utils.print_dict = mock.Mock()
        utils.save_image = mock.Mock()

    def assert_exits_with_msg(self, func, func_args, err_msg=None):
        with mock.patch.object(utils, 'exit') as mocked_utils_exit:
            mocked_utils_exit.return_value = '%s' % err_msg

            func(self.gc, func_args)
            if err_msg:
                mocked_utils_exit.assert_called_once_with(err_msg)
            else:
                mocked_utils_exit.assert_called_once_with()

    def _run_command(self, cmd):
        self.shell.main(cmd.split())

    @mock.patch('sys.stderr')
    def test_image_create_missing_disk_format(self, __):
        e = self.assertRaises(exc.CommandError, self._run_command,
                              '--os-image-api-version 2 image-create ' +
                              '--file fake_src --container-format bare')
        self.assertEqual('error: Must provide --disk-format when using '
                         '--file.', e.message)

    @mock.patch('sys.stderr')
    def test_image_create_missing_container_format(self, __):
        e = self.assertRaises(exc.CommandError, self._run_command,
                              '--os-image-api-version 2 image-create ' +
                              '--file fake_src --disk-format qcow2')
        self.assertEqual('error: Must provide --container-format when '
                         'using --file.', e.message)

    @mock.patch('sys.stderr')
    def test_image_create_missing_container_format_stdin_data(self, __):
        # Fake that get_data_file method returns data
        self.mock_get_data_file.return_value = six.StringIO()
        e = self.assertRaises(exc.CommandError, self._run_command,
                              '--os-image-api-version 2 image-create'
                              ' --disk-format qcow2')
        self.assertEqual('error: Must provide --container-format when '
                         'using stdin.', e.message)

    @mock.patch('sys.stderr')
    def test_image_create_missing_disk_format_stdin_data(self, __):
        # Fake that get_data_file method returns data
        self.mock_get_data_file.return_value = six.StringIO()
        e = self.assertRaises(exc.CommandError, self._run_command,
                              '--os-image-api-version 2 image-create'
                              ' --container-format bare')
        self.assertEqual('error: Must provide --disk-format when using stdin.',
                         e.message)

    def test_do_image_list(self):
        input = {
            'limit': None,
            'page_size': 18,
            'visibility': True,
            'member_status': 'Fake',
            'owner': 'test',
            'checksum': 'fake_checksum',
            'tag': 'fake tag',
            'properties': [],
            'sort_key': ['name', 'id'],
            'sort_dir': ['desc', 'asc'],
            'sort': None,
            'verbose': False
        }
        args = self._make_args(input)
        with mock.patch.object(self.gc.images, 'list') as mocked_list:
            mocked_list.return_value = {}

            test_shell.do_image_list(self.gc, args)

            exp_img_filters = {
                'owner': 'test',
                'member_status': 'Fake',
                'visibility': True,
                'checksum': 'fake_checksum',
                'tag': 'fake tag'
            }
            mocked_list.assert_called_once_with(page_size=18,
                                                sort_key=['name', 'id'],
                                                sort_dir=['desc', 'asc'],
                                                filters=exp_img_filters)
            utils.print_list.assert_called_once_with({}, ['ID', 'Name'])

    def test_do_image_list_with_single_sort_key(self):
        input = {
            'limit': None,
            'page_size': 18,
            'visibility': True,
            'member_status': 'Fake',
            'owner': 'test',
            'checksum': 'fake_checksum',
            'tag': 'fake tag',
            'properties': [],
            'sort_key': ['name'],
            'sort_dir': ['desc'],
            'sort': None,
            'verbose': False
        }
        args = self._make_args(input)
        with mock.patch.object(self.gc.images, 'list') as mocked_list:
            mocked_list.return_value = {}

            test_shell.do_image_list(self.gc, args)

            exp_img_filters = {
                'owner': 'test',
                'member_status': 'Fake',
                'visibility': True,
                'checksum': 'fake_checksum',
                'tag': 'fake tag'
            }
            mocked_list.assert_called_once_with(page_size=18,
                                                sort_key=['name'],
                                                sort_dir=['desc'],
                                                filters=exp_img_filters)
            utils.print_list.assert_called_once_with({}, ['ID', 'Name'])

    def test_do_image_list_new_sorting_syntax(self):
        input = {
            'limit': None,
            'page_size': 18,
            'visibility': True,
            'member_status': 'Fake',
            'owner': 'test',
            'checksum': 'fake_checksum',
            'tag': 'fake tag',
            'properties': [],
            'sort': 'name:desc,size:asc',
            'sort_key': [],
            'sort_dir': [],
            'verbose': False
        }
        args = self._make_args(input)
        with mock.patch.object(self.gc.images, 'list') as mocked_list:
            mocked_list.return_value = {}

            test_shell.do_image_list(self.gc, args)

            exp_img_filters = {
                'owner': 'test',
                'member_status': 'Fake',
                'visibility': True,
                'checksum': 'fake_checksum',
                'tag': 'fake tag'
            }
            mocked_list.assert_called_once_with(
                page_size=18,
                sort='name:desc,size:asc',
                filters=exp_img_filters)
            utils.print_list.assert_called_once_with({}, ['ID', 'Name'])

    def test_do_image_list_with_property_filter(self):
        input = {
            'limit': None,
            'page_size': 1,
            'visibility': True,
            'member_status': 'Fake',
            'owner': 'test',
            'checksum': 'fake_checksum',
            'tag': 'fake tag',
            'properties': ['os_distro=NixOS', 'architecture=x86_64'],
            'sort_key': ['name'],
            'sort_dir': ['desc'],
            'sort': None,
            'verbose': False
        }
        args = self._make_args(input)
        with mock.patch.object(self.gc.images, 'list') as mocked_list:
            mocked_list.return_value = {}

            test_shell.do_image_list(self.gc, args)

            exp_img_filters = {
                'owner': 'test',
                'member_status': 'Fake',
                'visibility': True,
                'checksum': 'fake_checksum',
                'tag': 'fake tag',
                'os_distro': 'NixOS',
                'architecture': 'x86_64'
            }

            mocked_list.assert_called_once_with(page_size=1,
                                                sort_key=['name'],
                                                sort_dir=['desc'],
                                                filters=exp_img_filters)
            utils.print_list.assert_called_once_with({}, ['ID', 'Name'])

    def test_do_image_show_human_readable(self):
        args = self._make_args({'id': 'pass', 'page_size': 18,
                                'human_readable': True,
                                'max_column_width': 120})
        with mock.patch.object(self.gc.images, 'get') as mocked_list:
            ignore_fields = ['self', 'access', 'file', 'schema']
            expect_image = dict([(field, field) for field in ignore_fields])
            expect_image['id'] = 'pass'
            expect_image['size'] = 1024
            mocked_list.return_value = expect_image

            test_shell.do_image_show(self.gc, args)

            mocked_list.assert_called_once_with('pass')
            utils.print_dict.assert_called_once_with({'id': 'pass',
                                                      'size': '1kB'},
                                                     max_column_width=120)

    def test_do_image_show(self):
        args = self._make_args({'id': 'pass', 'page_size': 18,
                                'human_readable': False,
                                'max_column_width': 120})
        with mock.patch.object(self.gc.images, 'get') as mocked_list:
            ignore_fields = ['self', 'access', 'file', 'schema']
            expect_image = dict([(field, field) for field in ignore_fields])
            expect_image['id'] = 'pass'
            expect_image['size'] = 1024
            mocked_list.return_value = expect_image

            test_shell.do_image_show(self.gc, args)

            mocked_list.assert_called_once_with('pass')
            utils.print_dict.assert_called_once_with({'id': 'pass',
                                                      'size': 1024},
                                                     max_column_width=120)

    @mock.patch('sys.stdin', autospec=True)
    def test_do_image_create_no_user_props(self, mock_stdin):
        args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd',
                                'container_format': 'bare',
                                'file': None})
        with mock.patch.object(self.gc.images, 'create') as mocked_create:
            ignore_fields = ['self', 'access', 'file', 'schema']
            expect_image = dict([(field, field) for field in ignore_fields])
            expect_image['id'] = 'pass'
            expect_image['name'] = 'IMG-01'
            expect_image['disk_format'] = 'vhd'
            expect_image['container_format'] = 'bare'
            mocked_create.return_value = expect_image

            # Ensure that the test stdin is not considered
            # to be supplying image data
            mock_stdin.isatty = lambda: True
            test_shell.do_image_create(self.gc, args)

            mocked_create.assert_called_once_with(name='IMG-01',
                                                  disk_format='vhd',
                                                  container_format='bare')
            utils.print_dict.assert_called_once_with({
                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
                'container_format': 'bare'})

    def test_do_image_create_with_file(self):
        self.mock_get_data_file.return_value = six.StringIO()
        try:
            file_name = None
            with open(tempfile.mktemp(), 'w+') as f:
                f.write('Some data here')
                f.flush()
                f.seek(0)
                file_name = f.name
            temp_args = {'name': 'IMG-01',
                         'disk_format': 'vhd',
                         'container_format': 'bare',
                         'file': file_name,
                         'progress': False}
            args = self._make_args(temp_args)
            with mock.patch.object(self.gc.images, 'create') as mocked_create:
                with mock.patch.object(self.gc.images, 'get') as mocked_get:

                    ignore_fields = ['self', 'access', 'schema']
                    expect_image = dict([(field, field) for field in
                                         ignore_fields])
                    expect_image['id'] = 'pass'
                    expect_image['name'] = 'IMG-01'
                    expect_image['disk_format'] = 'vhd'
                    expect_image['container_format'] = 'bare'
                    mocked_create.return_value = expect_image
                    mocked_get.return_value = expect_image

                    test_shell.do_image_create(self.gc, args)

                    temp_args.pop('file', None)
                    mocked_create.assert_called_once_with(**temp_args)
                    mocked_get.assert_called_once_with('pass')
                    utils.print_dict.assert_called_once_with({
                        'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
                        'container_format': 'bare'})
        finally:
            try:
                os.remove(f.name)
            except Exception:
                pass

    @mock.patch('sys.stdin', autospec=True)
    def test_do_image_create_with_unicode(self, mock_stdin):
        name = u'\u041f\u0420\u0418\u0412\u0415\u0422\u0418\u041a'

        args = self._make_args({'name': name,
                                'file': None})
        with mock.patch.object(self.gc.images, 'create') as mocked_create:
            ignore_fields = ['self', 'access', 'file', 'schema']
            expect_image = dict((field, field) for field in ignore_fields)
            expect_image['id'] = 'pass'
            expect_image['name'] = name
            mocked_create.return_value = expect_image

            mock_stdin.isatty = lambda: True
            test_shell.do_image_create(self.gc, args)

            mocked_create.assert_called_once_with(name=name)
            utils.print_dict.assert_called_once_with({
                'id': 'pass', 'name': name})

    @mock.patch('sys.stdin', autospec=True)
    def test_do_image_create_with_user_props(self, mock_stdin):
        args = self._make_args({'name': 'IMG-01',
                                'property': ['myprop=myval'],
                                'file': None,
                                'container_format': 'bare',
                                'disk_format': 'qcow2'})
        with mock.patch.object(self.gc.images, 'create') as mocked_create:
            ignore_fields = ['self', 'access', 'file', 'schema']
            expect_image = dict([(field, field) for field in ignore_fields])
            expect_image['id'] = 'pass'
            expect_image['name'] = 'IMG-01'
            expect_image['myprop'] = 'myval'
            mocked_create.return_value = expect_image

            # Ensure that the test stdin is not considered
            # to be supplying image data
            mock_stdin.isatty = lambda: True
            test_shell.do_image_create(self.gc, args)

            mocked_create.assert_called_once_with(name='IMG-01',
                                                  myprop='myval',
                                                  container_format='bare',
                                                  disk_format='qcow2')
            utils.print_dict.assert_called_once_with({
                'id': 'pass', 'name': 'IMG-01', 'myprop': 'myval'})

    def test_do_image_update_no_user_props(self):
        args = self._make_args({'id': 'pass', 'name': 'IMG-01',
                                'disk_format': 'vhd',
                                'container_format': 'bare'})
        with mock.patch.object(self.gc.images, 'update') as mocked_update:
            ignore_fields = ['self', 'access', 'file', 'schema']
            expect_image = dict([(field, field) for field in ignore_fields])
            expect_image['id'] = 'pass'
            expect_image['name'] = 'IMG-01'
            expect_image['disk_format'] = 'vhd'
            expect_image['container_format'] = 'bare'
            mocked_update.return_value = expect_image

            test_shell.do_image_update(self.gc, args)

            mocked_update.assert_called_once_with('pass',
                                                  None,
                                                  name='IMG-01',
                                                  disk_format='vhd',
                                                  container_format='bare')
            utils.print_dict.assert_called_once_with({
                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd',
                'container_format': 'bare'})

    def test_do_image_update_with_user_props(self):
        args = self._make_args({'id': 'pass', 'name': 'IMG-01',
                                'property': ['myprop=myval']})
        with mock.patch.object(self.gc.images, 'update') as mocked_update:
            ignore_fields = ['self', 'access', 'file', 'schema']
            expect_image = dict([(field, field) for field in ignore_fields])
            expect_image['id'] = 'pass'
            expect_image['name'] = 'IMG-01'
            expect_image['myprop'] = 'myval'
            mocked_update.return_value = expect_image

            test_shell.do_image_update(self.gc, args)

            mocked_update.assert_called_once_with('pass',
                                                  None,
                                                  name='IMG-01',
                                                  myprop='myval')
            utils.print_dict.assert_called_once_with({
                'id': 'pass', 'name': 'IMG-01', 'myprop': 'myval'})

    def test_do_image_update_with_remove_props(self):
        args = self._make_args({'id': 'pass', 'name': 'IMG-01',
                                'disk_format': 'vhd',
                                'remove-property': ['container_format']})
        with mock.patch.object(self.gc.images, 'update') as mocked_update:
            ignore_fields = ['self', 'access', 'file', 'schema']
            expect_image = dict([(field, field) for field in ignore_fields])
            expect_image['id'] = 'pass'
            expect_image['name'] = 'IMG-01'
            expect_image['disk_format'] = 'vhd'

            mocked_update.return_value = expect_image

            test_shell.do_image_update(self.gc, args)

            mocked_update.assert_called_once_with('pass',
                                                  ['container_format'],
                                                  name='IMG-01',
                                                  disk_format='vhd')
            utils.print_dict.assert_called_once_with({
                'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd'})

    def test_do_explain(self):
        input = {
            'page_size': 18,
            'id': 'pass',
            'schemas': 'test',
            'model': 'test',
        }
        args = self._make_args(input)
        with mock.patch.object(utils, 'print_list'):
            test_shell.do_explain(self.gc, args)

            self.gc.schemas.get.assert_called_once_with('test')

    def test_do_location_add(self):
        gc = self.gc
        loc = {'url': 'http://foo.com/', 'metadata': {'foo': 'bar'}}
        args = self._make_args({'id': 'pass',
                                'url': loc['url'],
                                'metadata': json.dumps(loc['metadata'])})
        with mock.patch.object(gc.images, 'add_location') as mocked_addloc:
            expect_image = {'id': 'pass', 'locations': [loc]}
            mocked_addloc.return_value = expect_image

            test_shell.do_location_add(self.gc, args)
            mocked_addloc.assert_called_once_with('pass',
                                                  loc['url'],
                                                  loc['metadata'])
            utils.print_dict.assert_called_once_with(expect_image)

    def test_do_location_delete(self):
        gc = self.gc
        loc_set = set(['http://foo/bar', 'http://spam/ham'])
        args = self._make_args({'id': 'pass', 'url': loc_set})

        with mock.patch.object(gc.images, 'delete_locations') as mocked_rmloc:
            test_shell.do_location_delete(self.gc, args)
            mocked_rmloc.assert_called_once_with('pass', loc_set)

    def test_do_location_update(self):
        gc = self.gc
        loc = {'url': 'http://foo.com/', 'metadata': {'foo': 'bar'}}
        args = self._make_args({'id': 'pass',
                                'url': loc['url'],
                                'metadata': json.dumps(loc['metadata'])})
        with mock.patch.object(gc.images, 'update_location') as mocked_modloc:
            expect_image = {'id': 'pass', 'locations': [loc]}
            mocked_modloc.return_value = expect_image

            test_shell.do_location_update(self.gc, args)
            mocked_modloc.assert_called_once_with('pass',
                                                  loc['url'],
                                                  loc['metadata'])
            utils.print_dict.assert_called_once_with(expect_image)

    def test_image_upload(self):
        args = self._make_args(
            {'id': 'IMG-01', 'file': 'test', 'size': 1024, 'progress': False})

        with mock.patch.object(self.gc.images, 'upload') as mocked_upload:
            utils.get_data_file = mock.Mock(return_value='testfile')
            mocked_upload.return_value = None
            test_shell.do_image_upload(self.gc, args)
            mocked_upload.assert_called_once_with('IMG-01', 'testfile', 1024)

    def test_image_download(self):
        args = self._make_args(
            {'id': 'IMG-01', 'file': 'test', 'progress': True})

        with mock.patch.object(self.gc.images, 'data') as mocked_data, \
                mock.patch.object(utils, '_extract_request_id'):
            mocked_data.return_value = utils.RequestIdProxy(
                [c for c in 'abcdef'])

            test_shell.do_image_download(self.gc, args)
            mocked_data.assert_called_once_with('IMG-01')

    @mock.patch.object(utils, 'exit')
    @mock.patch('sys.stdout', autospec=True)
    def test_image_download_no_file_arg(self, mocked_stdout,
                                        mocked_utils_exit):
        # Indicate that no file name was given as command line argument
        args = self._make_args({'id': '1234', 'file': None, 'progress': False})
        # Indicate that no file is specified for output redirection
        mocked_stdout.isatty = lambda: True
        test_shell.do_image_download(self.gc, args)
        mocked_utils_exit.assert_called_once_with(
            'No redirection or local file specified for downloaded image'
            ' data. Please specify a local file with --file to save'
            ' downloaded image or redirect output to another source.')

    def test_do_image_delete(self):
        args = argparse.Namespace(id=['image1', 'image2'])
        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
            mocked_delete.return_value = 0

            test_shell.do_image_delete(self.gc, args)
            self.assertEqual(2, mocked_delete.call_count)

    def test_do_image_deactivate(self):
        args = argparse.Namespace(id='image1')
        with mock.patch.object(self.gc.images,
                               'deactivate') as mocked_deactivate:
            mocked_deactivate.return_value = 0

            test_shell.do_image_deactivate(self.gc, args)
            self.assertEqual(1, mocked_deactivate.call_count)

    def test_do_image_reactivate(self):
        args = argparse.Namespace(id='image1')
        with mock.patch.object(self.gc.images,
                               'reactivate') as mocked_reactivate:
            mocked_reactivate.return_value = 0

            test_shell.do_image_reactivate(self.gc, args)
            self.assertEqual(1, mocked_reactivate.call_count)

    @mock.patch.object(utils, 'exit')
    @mock.patch.object(utils, 'print_err')
    def test_do_image_delete_with_invalid_ids(self, mocked_print_err,
                                              mocked_utils_exit):
        args = argparse.Namespace(id=['image1', 'image2'])
        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
            mocked_delete.side_effect = exc.HTTPNotFound

            test_shell.do_image_delete(self.gc, args)

            self.assertEqual(2, mocked_delete.call_count)
            self.assertEqual(2, mocked_print_err.call_count)
            mocked_utils_exit.assert_called_once_with()

    @mock.patch.object(utils, 'exit')
    @mock.patch.object(utils, 'print_err')
    def test_do_image_delete_with_forbidden_ids(self, mocked_print_err,
                                                mocked_utils_exit):
        args = argparse.Namespace(id=['image1', 'image2'])
        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
            mocked_delete.side_effect = exc.HTTPForbidden

            test_shell.do_image_delete(self.gc, args)

            self.assertEqual(2, mocked_delete.call_count)
            self.assertEqual(2, mocked_print_err.call_count)
            mocked_utils_exit.assert_called_once_with()

    @mock.patch.object(utils, 'exit')
    @mock.patch.object(utils, 'print_err')
    def test_do_image_delete_with_image_in_use(self, mocked_print_err,
                                               mocked_utils_exit):
        args = argparse.Namespace(id=['image1', 'image2'])
        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
            mocked_delete.side_effect = exc.HTTPConflict

            test_shell.do_image_delete(self.gc, args)

            self.assertEqual(2, mocked_delete.call_count)
            self.assertEqual(2, mocked_print_err.call_count)
            mocked_utils_exit.assert_called_once_with()

    def test_do_image_delete_deleted(self):
        image_id = 'deleted-img'
        args = argparse.Namespace(id=[image_id])
        with mock.patch.object(self.gc.images, 'delete') as mocked_delete:
            mocked_delete.side_effect = exc.HTTPNotFound

            self.assert_exits_with_msg(func=test_shell.do_image_delete,
                                       func_args=args)

    @mock.patch.object(utils, 'print_err')
    def test_do_image_download_with_forbidden_id(self, mocked_print_err):
        args = self._make_args({'id': 'IMG-01', 'file': None,
                                'progress': False})
        with mock.patch.object(self.gc.images, 'data') as mocked_data:
            mocked_data.side_effect = exc.HTTPForbidden
            try:
                test_shell.do_image_download(self.gc, args)
                self.fail('Exit not called')
            except SystemExit:
                pass

            self.assertEqual(1, mocked_data.call_count)
            self.assertEqual(1, mocked_print_err.call_count)

    @mock.patch.object(utils, 'print_err')
    def test_do_image_download_with_500(self, mocked_print_err):
        args = self._make_args({'id': 'IMG-01', 'file': None,
                                'progress': False})
        with mock.patch.object(self.gc.images, 'data') as mocked_data:
            mocked_data.side_effect = exc.HTTPInternalServerError
            try:
                test_shell.do_image_download(self.gc, args)
                self.fail('Exit not called')
            except SystemExit:
                pass

            self.assertEqual(1, mocked_data.call_count)
            self.assertEqual(1, mocked_print_err.call_count)

    def test_do_member_list(self):
        args = self._make_args({'image_id': 'IMG-01'})
        with mock.patch.object(self.gc.image_members, 'list') as mocked_list:
            mocked_list.return_value = {}

            test_shell.do_member_list(self.gc, args)

            mocked_list.assert_called_once_with('IMG-01')
            columns = ['Image ID', 'Member ID', 'Status']
            utils.print_list.assert_called_once_with({}, columns)

    def test_do_member_create(self):
        args = self._make_args({'image_id': 'IMG-01', 'member_id': 'MEM-01'})
        with mock.patch.object(self.gc.image_members, 'create') as mock_create:
            mock_create.return_value = {}

            test_shell.do_member_create(self.gc, args)

            mock_create.assert_called_once_with('IMG-01', 'MEM-01')
            columns = ['Image ID', 'Member ID', 'Status']
            utils.print_list.assert_called_once_with([{}], columns)

    def test_do_member_create_with_few_arguments(self):
        args = self._make_args({'image_id': None, 'member_id': 'MEM-01'})
        msg = 'Unable to create member. Specify image_id and member_id'

        self.assert_exits_with_msg(func=test_shell.do_member_create,
                                   func_args=args,
                                   err_msg=msg)

    def test_do_member_update(self):
        input = {
            'image_id': 'IMG-01',
            'member_id': 'MEM-01',
            'member_status': 'status',
        }
        args = self._make_args(input)
        with mock.patch.object(self.gc.image_members, 'update') as mock_update:
            mock_update.return_value = {}

            test_shell.do_member_update(self.gc, args)

            mock_update.assert_called_once_with('IMG-01', 'MEM-01', 'status')
            columns = ['Image ID', 'Member ID', 'Status']
            utils.print_list.assert_called_once_with([{}], columns)

    def test_do_member_update_with_few_arguments(self):
        input = {
            'image_id': 'IMG-01',
            'member_id': 'MEM-01',
            'member_status': None,
        }
        args = self._make_args(input)
        msg = 'Unable to update member. Specify image_id, member_id' \
              ' and member_status'

        self.assert_exits_with_msg(func=test_shell.do_member_update,
                                   func_args=args,
                                   err_msg=msg)

    def test_do_member_delete(self):
        args = self._make_args({'image_id': 'IMG-01', 'member_id': 'MEM-01'})
        with mock.patch.object(self.gc.image_members, 'delete') as mock_delete:
            test_shell.do_member_delete(self.gc, args)

            mock_delete.assert_called_once_with('IMG-01', 'MEM-01')

    def test_do_member_delete_with_few_arguments(self):
        args = self._make_args({'image_id': None, 'member_id': 'MEM-01'})
        msg = 'Unable to delete member. Specify image_id and member_id'

        self.assert_exits_with_msg(func=test_shell.do_member_delete,
                                   func_args=args,
                                   err_msg=msg)

    def test_image_tag_update(self):
        args = self._make_args({'image_id': 'IMG-01', 'tag_value': 'tag01'})
        with mock.patch.object(self.gc.image_tags, 'update') as mocked_update:
            self.gc.images.get = mock.Mock(return_value={})
            mocked_update.return_value = None

            test_shell.do_image_tag_update(self.gc, args)

            mocked_update.assert_called_once_with('IMG-01', 'tag01')

    def test_image_tag_update_with_few_arguments(self):
        args = self._make_args({'image_id': None, 'tag_value': 'tag01'})
        msg = 'Unable to update tag. Specify image_id and tag_value'

        self.assert_exits_with_msg(func=test_shell.do_image_tag_update,
                                   func_args=args,
                                   err_msg=msg)

    def test_image_tag_delete(self):
        args = self._make_args({'image_id': 'IMG-01', 'tag_value': 'tag01'})
        with mock.patch.object(self.gc.image_tags, 'delete') as mocked_delete:
            mocked_delete.return_value = None

            test_shell.do_image_tag_delete(self.gc, args)

            mocked_delete.assert_called_once_with('IMG-01', 'tag01')

    def test_image_tag_delete_with_few_arguments(self):
        args = self._make_args({'image_id': 'IMG-01', 'tag_value': None})
        msg = 'Unable to delete tag. Specify image_id and tag_value'

        self.assert_exits_with_msg(func=test_shell.do_image_tag_delete,
                                   func_args=args,
                                   err_msg=msg)

    def test_do_md_namespace_create(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'protected': True})
        with mock.patch.object(self.gc.metadefs_namespace,
                               'create') as mocked_create:
            expect_namespace = {
                'namespace': 'MyNamespace',
                'protected': True
            }

            mocked_create.return_value = expect_namespace

            test_shell.do_md_namespace_create(self.gc, args)

            mocked_create.assert_called_once_with(namespace='MyNamespace',
                                                  protected=True)
            utils.print_dict.assert_called_once_with(expect_namespace)

    def test_do_md_namespace_import(self):
        args = self._make_args({'file': 'test'})

        expect_namespace = {
            'namespace': 'MyNamespace',
            'protected': True
        }

        with mock.patch.object(self.gc.metadefs_namespace,
                               'create') as mocked_create:
            mock_read = mock.Mock(return_value=json.dumps(expect_namespace))
            mock_file = mock.Mock(read=mock_read)
            utils.get_data_file = mock.Mock(return_value=mock_file)
            mocked_create.return_value = expect_namespace

            test_shell.do_md_namespace_import(self.gc, args)

            mocked_create.assert_called_once_with(**expect_namespace)
            utils.print_dict.assert_called_once_with(expect_namespace)

    def test_do_md_namespace_import_invalid_json(self):
        args = self._make_args({'file': 'test'})
        mock_read = mock.Mock(return_value='Invalid')
        mock_file = mock.Mock(read=mock_read)
        utils.get_data_file = mock.Mock(return_value=mock_file)

        self.assertRaises(SystemExit, test_shell.do_md_namespace_import,
                          self.gc, args)

    def test_do_md_namespace_import_no_input(self):
        args = self._make_args({'file': None})
        utils.get_data_file = mock.Mock(return_value=None)

        self.assertRaises(SystemExit, test_shell.do_md_namespace_import,
                          self.gc, args)

    def test_do_md_namespace_update(self):
        args = self._make_args({'id': 'MyNamespace',
                                'protected': True})
        with mock.patch.object(self.gc.metadefs_namespace,
                               'update') as mocked_update:
            expect_namespace = {
                'namespace': 'MyNamespace',
                'protected': True
            }

            mocked_update.return_value = expect_namespace

            test_shell.do_md_namespace_update(self.gc, args)

            mocked_update.assert_called_once_with('MyNamespace',
                                                  id='MyNamespace',
                                                  protected=True)
            utils.print_dict.assert_called_once_with(expect_namespace)

    def test_do_md_namespace_show(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'max_column_width': 80,
                                'resource_type': None})
        with mock.patch.object(self.gc.metadefs_namespace,
                               'get') as mocked_get:
            expect_namespace = {'namespace': 'MyNamespace'}

            mocked_get.return_value = expect_namespace

            test_shell.do_md_namespace_show(self.gc, args)

            mocked_get.assert_called_once_with('MyNamespace')
            utils.print_dict.assert_called_once_with(expect_namespace, 80)

    def test_do_md_namespace_show_resource_type(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'max_column_width': 80,
                                'resource_type': 'RESOURCE'})
        with mock.patch.object(self.gc.metadefs_namespace,
                               'get') as mocked_get:
            expect_namespace = {'namespace': 'MyNamespace'}

            mocked_get.return_value = expect_namespace

            test_shell.do_md_namespace_show(self.gc, args)

            mocked_get.assert_called_once_with('MyNamespace',
                                               resource_type='RESOURCE')
            utils.print_dict.assert_called_once_with(expect_namespace, 80)

    def test_do_md_namespace_list(self):
        args = self._make_args({'resource_type': None,
                                'visibility': None,
                                'page_size': None})
        with mock.patch.object(self.gc.metadefs_namespace,
                               'list') as mocked_list:
            expect_namespaces = [{'namespace': 'MyNamespace'}]

            mocked_list.return_value = expect_namespaces

            test_shell.do_md_namespace_list(self.gc, args)

            mocked_list.assert_called_once_with(filters={})
            utils.print_list.assert_called_once_with(expect_namespaces,
                                                     ['namespace'])

    def test_do_md_namespace_list_page_size(self):
        args = self._make_args({'resource_type': None,
                                'visibility': None,
                                'page_size': 2})
        with mock.patch.object(self.gc.metadefs_namespace,
                               'list') as mocked_list:
            expect_namespaces = [{'namespace': 'MyNamespace'}]

            mocked_list.return_value = expect_namespaces

            test_shell.do_md_namespace_list(self.gc, args)

            mocked_list.assert_called_once_with(filters={}, page_size=2)
            utils.print_list.assert_called_once_with(expect_namespaces,
                                                     ['namespace'])

    def test_do_md_namespace_list_one_filter(self):
        args = self._make_args({'resource_types': ['OS::Compute::Aggregate'],
                                'visibility': None,
                                'page_size': None})
        with mock.patch.object(self.gc.metadefs_namespace, 'list') as \
                mocked_list:
            expect_namespaces = [{'namespace': 'MyNamespace'}]

            mocked_list.return_value = expect_namespaces

            test_shell.do_md_namespace_list(self.gc, args)

            mocked_list.assert_called_once_with(filters={
                'resource_types': ['OS::Compute::Aggregate']})
            utils.print_list.assert_called_once_with(expect_namespaces,
                                                     ['namespace'])

    def test_do_md_namespace_list_all_filters(self):
        args = self._make_args({'resource_types': ['OS::Compute::Aggregate'],
                                'visibility': 'public',
                                'page_size': None})
        with mock.patch.object(self.gc.metadefs_namespace,
                               'list') as mocked_list:
            expect_namespaces = [{'namespace': 'MyNamespace'}]

            mocked_list.return_value = expect_namespaces

            test_shell.do_md_namespace_list(self.gc, args)

            mocked_list.assert_called_once_with(filters={
                'resource_types': ['OS::Compute::Aggregate'],
                'visibility': 'public'})
            utils.print_list.assert_called_once_with(expect_namespaces,
                                                     ['namespace'])

    def test_do_md_namespace_list_unknown_filter(self):
        args = self._make_args({'resource_type': None,
                                'visibility': None,
                                'some_arg': 'some_value',
                                'page_size': None})
        with mock.patch.object(self.gc.metadefs_namespace,
                               'list') as mocked_list:
            expect_namespaces = [{'namespace': 'MyNamespace'}]

            mocked_list.return_value = expect_namespaces

            test_shell.do_md_namespace_list(self.gc, args)

            mocked_list.assert_called_once_with(filters={})
            utils.print_list.assert_called_once_with(expect_namespaces,
                                                     ['namespace'])

    def test_do_md_namespace_delete(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'content': False})
        with mock.patch.object(self.gc.metadefs_namespace, 'delete') as \
                mocked_delete:
            test_shell.do_md_namespace_delete(self.gc, args)

            mocked_delete.assert_called_once_with('MyNamespace')

    def test_do_md_resource_type_associate(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'name': 'MyResourceType',
                                'prefix': 'PREFIX:'})
        with mock.patch.object(self.gc.metadefs_resource_type,
                               'associate') as mocked_associate:
            expect_rt = {
                'namespace': 'MyNamespace',
                'name': 'MyResourceType',
                'prefix': 'PREFIX:'
            }

            mocked_associate.return_value = expect_rt

            test_shell.do_md_resource_type_associate(self.gc, args)

            mocked_associate.assert_called_once_with('MyNamespace',
                                                     **expect_rt)
            utils.print_dict.assert_called_once_with(expect_rt)

    def test_do_md_resource_type_deassociate(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'resource_type': 'MyResourceType'})
        with mock.patch.object(self.gc.metadefs_resource_type,
                               'deassociate') as mocked_deassociate:
            test_shell.do_md_resource_type_deassociate(self.gc, args)

            mocked_deassociate.assert_called_once_with('MyNamespace',
                                                       'MyResourceType')

    def test_do_md_resource_type_list(self):
        args = self._make_args({})
        with mock.patch.object(self.gc.metadefs_resource_type,
                               'list') as mocked_list:
            expect_objects = ['MyResourceType1', 'MyResourceType2']

            mocked_list.return_value = expect_objects

            test_shell.do_md_resource_type_list(self.gc, args)

            self.assertEqual(1, mocked_list.call_count)

    def test_do_md_namespace_resource_type_list(self):
        args = self._make_args({'namespace': 'MyNamespace'})
        with mock.patch.object(self.gc.metadefs_resource_type,
                               'get') as mocked_get:
            expect_objects = [{'namespace': 'MyNamespace',
                               'object': 'MyObject'}]

            mocked_get.return_value = expect_objects

            test_shell.do_md_namespace_resource_type_list(self.gc, args)

            mocked_get.assert_called_once_with('MyNamespace')
            utils.print_list.assert_called_once_with(expect_objects,
                                                     ['name', 'prefix',
                                                      'properties_target'])

    def test_do_md_property_create(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'name': "MyProperty",
                                'title': "Title",
                                'schema': '{}'})
        with mock.patch.object(self.gc.metadefs_property,
                               'create') as mocked_create:
            expect_property = {
                'namespace': 'MyNamespace',
                'name': 'MyProperty',
                'title': 'Title'
            }

            mocked_create.return_value = expect_property

            test_shell.do_md_property_create(self.gc, args)

            mocked_create.assert_called_once_with('MyNamespace',
                                                  name='MyProperty',
                                                  title='Title')
            utils.print_dict.assert_called_once_with(expect_property)

    def test_do_md_property_create_invalid_schema(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'name': "MyProperty",
                                'title': "Title",
                                'schema': 'Invalid'})
        self.assertRaises(SystemExit, test_shell.do_md_property_create,
                          self.gc, args)

    def test_do_md_property_update(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'property': 'MyProperty',
                                'name': 'NewName',
                                'title': "Title",
                                'schema': '{}'})
        with mock.patch.object(self.gc.metadefs_property,
                               'update') as mocked_update:
            expect_property = {
                'namespace': 'MyNamespace',
                'name': 'MyProperty',
                'title': 'Title'
            }

            mocked_update.return_value = expect_property

            test_shell.do_md_property_update(self.gc, args)

            mocked_update.assert_called_once_with('MyNamespace', 'MyProperty',
                                                  name='NewName',
                                                  title='Title')
            utils.print_dict.assert_called_once_with(expect_property)

    def test_do_md_property_update_invalid_schema(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'property': 'MyProperty',
                                'name': "MyObject",
                                'title': "Title",
                                'schema': 'Invalid'})
        self.assertRaises(SystemExit, test_shell.do_md_property_update,
                          self.gc, args)

    def test_do_md_property_show(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'property': 'MyProperty',
                                'max_column_width': 80})
        with mock.patch.object(self.gc.metadefs_property, 'get') as mocked_get:
            expect_property = {
                'namespace': 'MyNamespace',
                'property': 'MyProperty',
                'title': 'Title'
            }

            mocked_get.return_value = expect_property

            test_shell.do_md_property_show(self.gc, args)

            mocked_get.assert_called_once_with('MyNamespace', 'MyProperty')
            utils.print_dict.assert_called_once_with(expect_property, 80)

    def test_do_md_property_delete(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'property': 'MyProperty'})
        with mock.patch.object(self.gc.metadefs_property,
                               'delete') as mocked_delete:
            test_shell.do_md_property_delete(self.gc, args)

            mocked_delete.assert_called_once_with('MyNamespace', 'MyProperty')

    def test_do_md_namespace_property_delete(self):
        args = self._make_args({'namespace': 'MyNamespace'})
        with mock.patch.object(self.gc.metadefs_property,
                               'delete_all') as mocked_delete_all:
            test_shell.do_md_namespace_properties_delete(self.gc, args)

            mocked_delete_all.assert_called_once_with('MyNamespace')

    def test_do_md_property_list(self):
        args = self._make_args({'namespace': 'MyNamespace'})
        with mock.patch.object(self.gc.metadefs_property,
                               'list') as mocked_list:
            expect_objects = [{'namespace': 'MyNamespace',
                               'property': 'MyProperty',
                               'title': 'MyTitle'}]

            mocked_list.return_value = expect_objects

            test_shell.do_md_property_list(self.gc, args)

            mocked_list.assert_called_once_with('MyNamespace')
            utils.print_list.assert_called_once_with(expect_objects,
                                                     ['name', 'title', 'type'])

    def test_do_md_object_create(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'name': "MyObject",
                                'schema': '{}'})
        with mock.patch.object(self.gc.metadefs_object,
                               'create') as mocked_create:
            expect_object = {
                'namespace': 'MyNamespace',
                'name': 'MyObject'
            }

            mocked_create.return_value = expect_object

            test_shell.do_md_object_create(self.gc, args)

            mocked_create.assert_called_once_with('MyNamespace',
                                                  name='MyObject')
            utils.print_dict.assert_called_once_with(expect_object)

    def test_do_md_object_create_invalid_schema(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'name': "MyObject",
                                'schema': 'Invalid'})
        self.assertRaises(SystemExit, test_shell.do_md_object_create,
                          self.gc, args)

    def test_do_md_object_update(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'object': 'MyObject',
                                'name': 'NewName',
                                'schema': '{}'})
        with mock.patch.object(self.gc.metadefs_object,
                               'update') as mocked_update:
            expect_object = {
                'namespace': 'MyNamespace',
                'name': 'MyObject'
            }

            mocked_update.return_value = expect_object

            test_shell.do_md_object_update(self.gc, args)

            mocked_update.assert_called_once_with('MyNamespace', 'MyObject',
                                                  name='NewName')
            utils.print_dict.assert_called_once_with(expect_object)

    def test_do_md_object_update_invalid_schema(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'object': 'MyObject',
                                'name': "MyObject",
                                'schema': 'Invalid'})
        self.assertRaises(SystemExit, test_shell.do_md_object_update,
                          self.gc, args)

    def test_do_md_object_show(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'object': 'MyObject',
                                'max_column_width': 80})
        with mock.patch.object(self.gc.metadefs_object, 'get') as mocked_get:
            expect_object = {
                'namespace': 'MyNamespace',
                'object': 'MyObject'
            }

            mocked_get.return_value = expect_object

            test_shell.do_md_object_show(self.gc, args)

            mocked_get.assert_called_once_with('MyNamespace', 'MyObject')
            utils.print_dict.assert_called_once_with(expect_object, 80)

    def test_do_md_object_property_show(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'object': 'MyObject',
                                'property': 'MyProperty',
                                'max_column_width': 80})
        with mock.patch.object(self.gc.metadefs_object, 'get') as mocked_get:
            expect_object = {'name': 'MyObject',
                             'properties': {
                                 'MyProperty': {'type': 'string'}
                             }}

            mocked_get.return_value = expect_object

            test_shell.do_md_object_property_show(self.gc, args)

            mocked_get.assert_called_once_with('MyNamespace', 'MyObject')
            utils.print_dict.assert_called_once_with({'type': 'string',
                                                      'name': 'MyProperty'},
                                                     80)

    def test_do_md_object_property_show_non_existing(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'object': 'MyObject',
                                'property': 'MyProperty',
                                'max_column_width': 80})
        with mock.patch.object(self.gc.metadefs_object, 'get') as mocked_get:
            expect_object = {'name': 'MyObject', 'properties': {}}
            mocked_get.return_value = expect_object

            self.assertRaises(SystemExit,
                              test_shell.do_md_object_property_show,
                              self.gc, args)
            mocked_get.assert_called_once_with('MyNamespace', 'MyObject')

    def test_do_md_object_delete(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'object': 'MyObject'})
        with mock.patch.object(self.gc.metadefs_object,
                               'delete') as mocked_delete:
            test_shell.do_md_object_delete(self.gc, args)

            mocked_delete.assert_called_once_with('MyNamespace', 'MyObject')

    def test_do_md_namespace_objects_delete(self):
        args = self._make_args({'namespace': 'MyNamespace'})
        with mock.patch.object(self.gc.metadefs_object,
                               'delete_all') as mocked_delete_all:
            test_shell.do_md_namespace_objects_delete(self.gc, args)

            mocked_delete_all.assert_called_once_with('MyNamespace')

    def test_do_md_object_list(self):
        args = self._make_args({'namespace': 'MyNamespace'})
        with mock.patch.object(self.gc.metadefs_object, 'list') as mocked_list:
            expect_objects = [{'namespace': 'MyNamespace',
                               'object': 'MyObject'}]

            mocked_list.return_value = expect_objects

            test_shell.do_md_object_list(self.gc, args)

            mocked_list.assert_called_once_with('MyNamespace')
            utils.print_list.assert_called_once_with(
                expect_objects,
                ['name', 'description'],
                field_settings={
                    'description': {'align': 'l', 'max_width': 50}})

    def test_do_md_tag_create(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'name': 'MyTag'})
        with mock.patch.object(self.gc.metadefs_tag,
                               'create') as mocked_create:
            expect_tag = {
                'namespace': 'MyNamespace',
                'name': 'MyTag'
            }

            mocked_create.return_value = expect_tag

            test_shell.do_md_tag_create(self.gc, args)

            mocked_create.assert_called_once_with('MyNamespace', 'MyTag')
            utils.print_dict.assert_called_once_with(expect_tag)

    def test_do_md_tag_update(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'tag': 'MyTag',
                                'name': 'NewTag'})
        with mock.patch.object(self.gc.metadefs_tag,
                               'update') as mocked_update:
            expect_tag = {
                'namespace': 'MyNamespace',
                'name': 'NewTag'
            }

            mocked_update.return_value = expect_tag

            test_shell.do_md_tag_update(self.gc, args)

            mocked_update.assert_called_once_with('MyNamespace', 'MyTag',
                                                  name='NewTag')
            utils.print_dict.assert_called_once_with(expect_tag)

    def test_do_md_tag_show(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'tag': 'MyTag',
                                'sort_dir': 'desc'})
        with mock.patch.object(self.gc.metadefs_tag, 'get') as mocked_get:
            expect_tag = {
                'namespace': 'MyNamespace',
                'tag': 'MyTag'
            }

            mocked_get.return_value = expect_tag

            test_shell.do_md_tag_show(self.gc, args)

            mocked_get.assert_called_once_with('MyNamespace', 'MyTag')
            utils.print_dict.assert_called_once_with(expect_tag)

    def test_do_md_tag_delete(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'tag': 'MyTag'})
        with mock.patch.object(self.gc.metadefs_tag,
                               'delete') as mocked_delete:
            test_shell.do_md_tag_delete(self.gc, args)

            mocked_delete.assert_called_once_with('MyNamespace', 'MyTag')

    def test_do_md_namespace_tags_delete(self):
        args = self._make_args({'namespace': 'MyNamespace'})
        with mock.patch.object(self.gc.metadefs_tag,
                               'delete_all') as mocked_delete_all:
            test_shell.do_md_namespace_tags_delete(self.gc, args)

            mocked_delete_all.assert_called_once_with('MyNamespace')

    def test_do_md_tag_list(self):
        args = self._make_args({'namespace': 'MyNamespace'})
        with mock.patch.object(self.gc.metadefs_tag, 'list') as mocked_list:
            expect_tags = [{'namespace': 'MyNamespace',
                            'tag': 'MyTag'}]

            mocked_list.return_value = expect_tags

            test_shell.do_md_tag_list(self.gc, args)

            mocked_list.assert_called_once_with('MyNamespace')
            utils.print_list.assert_called_once_with(
                expect_tags,
                ['name'],
                field_settings={
                    'description': {'align': 'l', 'max_width': 50}})

    def test_do_md_tag_create_multiple(self):
        args = self._make_args({'namespace': 'MyNamespace',
                                'delim': ',',
                                'names': 'MyTag1, MyTag2'})
        with mock.patch.object(
                self.gc.metadefs_tag, 'create_multiple') as mocked_create_tags:
            expect_tags = [{'tags': [{'name': 'MyTag1'}, {'name': 'MyTag2'}]}]

            mocked_create_tags.return_value = expect_tags

            test_shell.do_md_tag_create_multiple(self.gc, args)

            mocked_create_tags.assert_called_once_with(
                'MyNamespace', tags=['MyTag1', 'MyTag2'])
            utils.print_list.assert_called_once_with(
                expect_tags,
                ['name'],
                field_settings={
                    'description': {'align': 'l', 'max_width': 50}})
