From 25dd5170e27eb0ed117a4c87294787a1248a6321 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 28 Oct 2016 12:35:16 -0500 Subject: [PATCH] Add unit tests for cratonclient.shell.v1.cells_shell This adds a number of unit tests around the cells_shell logic that should allow our integration tests to start operating differently. We will likely end-up extracting some of the common logic for the _shell unit test modules to a base test case class so we can use those methods across all the unittests more conveniently. Change-Id: I60a6e7c89aec6c1104177b92b6e2e358adfe01d5 --- cratonclient/shell/v1/cells_shell.py | 91 ++-- .../tests/integration/test_cells_shell.py | 34 +- cratonclient/tests/unit/shell/__init__.py | 1 + cratonclient/tests/unit/shell/v1/__init__.py | 1 + .../tests/unit/shell/v1/test_cells_shell.py | 463 ++++++++++++++++++ 5 files changed, 523 insertions(+), 67 deletions(-) create mode 100644 cratonclient/tests/unit/shell/__init__.py create mode 100644 cratonclient/tests/unit/shell/v1/__init__.py create mode 100644 cratonclient/tests/unit/shell/v1/test_cells_shell.py diff --git a/cratonclient/shell/v1/cells_shell.py b/cratonclient/shell/v1/cells_shell.py index 994380b..d2b7a05 100644 --- a/cratonclient/shell/v1/cells_shell.py +++ b/cratonclient/shell/v1/cells_shell.py @@ -12,9 +12,11 @@ # License for the specific language governing permissions and limitations # under the License. """Cells resource and resource shell wrapper.""" +from __future__ import print_function + from cratonclient.common import cliutils from cratonclient import exceptions as exc -from cratonclient.v1.cells import CELL_FIELDS as c_fields +from cratonclient.v1 import cells @cliutils.arg('region', @@ -28,7 +30,7 @@ from cratonclient.v1.cells import CELL_FIELDS as c_fields def do_cell_show(cc, args): """Show detailed information about a cell.""" cell = cc.inventory(args.region).cells.get(args.id) - data = {f: getattr(cell, f, '') for f in c_fields} + data = {f: getattr(cell, f, '') for f in cells.CELL_FIELDS} cliutils.print_dict(data, wrap=72) @@ -51,6 +53,7 @@ def do_cell_show(cc, args): @cliutils.arg('--sort-dir', metavar='', default='asc', + choices=('asc', 'desc'), help='Sort direction: "asc" (default) or "desc".') @cliutils.arg('--fields', nargs='+', @@ -69,45 +72,41 @@ def do_cell_list(cc, args): 'non-negative limit, got {0}' .format(args.limit)) params['limit'] = args.limit + + if args.fields and args.detail: + raise exc.CommandError('Cannot specify both --fields and --detail.') + if args.detail: - fields = c_fields + fields = cells.CELL_FIELDS params['detail'] = args.detail elif args.fields: - fields = {x: c_fields[x] for x in args.fields} - else: - fields = {x: c_fields[x] for x in default_fields} - if args.sort_key is not None: - fields_map = dict(zip(fields.keys(), fields.keys())) - # TODO(cmspence): Do we want to allow sorting by field heading value? try: - sort_key = fields_map[args.sort_key] - except KeyError: + fields = {x: cells.CELL_FIELDS[x] for x in args.fields} + except KeyError as keyerr: + raise exc.CommandError('Invalid field "{}"'.format(keyerr.args[0])) + else: + fields = {x: cells.CELL_FIELDS[x] for x in default_fields} + sort_key = args.sort_key and args.sort_key.lower() + if sort_key is not None: + if sort_key not in cells.CELL_FIELDS: raise exc.CommandError( - '{0} is an invalid key for sorting, valid values for ' - '--sort-key are: {1}'.format(args.sort_key, c_fields.keys()) + ('"--sort-key" value was "{}" but should ' + 'be one of: {}').format( + args.sort_key, + ', '.join(cells.CELL_FIELDS.keys()) + ) ) params['sort_key'] = sort_key - if args.sort_dir is not None: - if args.sort_dir not in ('asc', 'desc'): - raise exc.CommandError('Invalid sort direction specified. The ' - 'expected valid values for --sort-dir ' - 'are: "asc", "desc".') - params['sort_dir'] = args.sort_dir + params['sort_dir'] = args.sort_dir - cells = cc.inventory(args.region).cells.list(**params) - cliutils.print_list(cells, list(fields)) + listed_cells = cc.inventory(args.region).cells.list(**params) + cliutils.print_list(listed_cells, list(fields)) @cliutils.arg('-n', '--name', metavar='', required=True, help='Name of the cell.') -@cliutils.arg('-p', '--project', - dest='project_id', - metavar='', - type=int, - required=True, - help='ID of the project that the cell belongs to.') @cliutils.arg('-r', '--region', dest='region_id', metavar='', @@ -119,9 +118,9 @@ def do_cell_list(cc, args): def do_cell_create(cc, args): """Register a new cell with the Craton service.""" fields = {k: v for (k, v) in vars(args).items() - if k in c_fields and not (v is None)} + if k in cells.CELL_FIELDS and not (v is None)} cell = cc.inventory(args.region_id).cells.create(**fields) - data = {f: getattr(cell, f, '') for f in c_fields} + data = {f: getattr(cell, f, '') for f in cells.CELL_FIELDS} cliutils.print_dict(data, wrap=72) @@ -135,28 +134,26 @@ def do_cell_create(cc, args): help='ID of the cell.') @cliutils.arg('-n', '--name', metavar='', - required=True, help='Name of the cell.') -@cliutils.arg('-p', '--project', - dest='project_id', - metavar='', - type=int, - required=True, - help='Desired ID of the project that the cell should change to.') @cliutils.arg('-r', '--region', dest='region_id', metavar='', type=int, - required=True, help='Desired ID of the region that the cell should change to.') @cliutils.arg('--note', help='Note about the cell.') def do_cell_update(cc, args): """Update a cell that is registered with the Craton service.""" fields = {k: v for (k, v) in vars(args).items() - if k in c_fields and not (v is None)} - cell = cc.inventory(args.region).cells.update(**fields) - data = {f: getattr(cell, f, '') for f in c_fields} + if k in cells.CELL_FIELDS and not (v is None)} + cell_id = fields.pop('id') + if not fields: + raise exc.CommandError( + 'Nothing to update... Please specify one of --name, --region, ' + 'or --note' + ) + cell = cc.inventory(args.region).cells.update(cell_id, **fields) + data = {f: getattr(cell, f, '') for f in cells.CELL_FIELDS} cliutils.print_dict(data, wrap=72) @@ -170,6 +167,14 @@ def do_cell_update(cc, args): help='ID of the cell.') def do_cell_delete(cc, args): """Delete a cell that is registered with the Craton service.""" - response = cc.inventory(args.region).cells.delete(args.id) - print("Cell {0} was {1}successfully deleted.". - format(args.id, '' if response else 'un')) + try: + response = cc.inventory(args.region).cells.delete(args.id) + except exc.ClientException as client_exc: + raise exc.CommandError( + 'Failed to delete cell {} due to "{}:{}"'.format( + args.id, client_exc.__class__, str(client_exc) + ) + ) + else: + print("Cell {0} was {1} deleted.". + format(args.id, 'successfully' if response else 'not')) diff --git a/cratonclient/tests/integration/test_cells_shell.py b/cratonclient/tests/integration/test_cells_shell.py index 52e3e15..386d7d3 100644 --- a/cratonclient/tests/integration/test_cells_shell.py +++ b/cratonclient/tests/integration/test_cells_shell.py @@ -58,7 +58,7 @@ class TestCellsShell(base.ShellTestCase): def test_cell_list_limit_0_success(self, mock_list): """Verify that --limit 0 prints out all project cells.""" self.shell('cell-list -r 1 --limit 0') - mock_list.assert_called_once_with(limit=0) + mock_list.assert_called_once_with(limit=0, sort_dir='asc') @mock.patch('cratonclient.v1.cells.CellManager.list') def test_cell_list_limit_positive_num_success(self, mock_list): @@ -67,7 +67,7 @@ class TestCellsShell(base.ShellTestCase): The command will print out X number of project cells. """ self.shell('cell-list -r 1 --limit 1') - mock_list.assert_called_once_with(limit=1) + mock_list.assert_called_once_with(limit=1, sort_dir='asc') def test_cell_list_limit_negative_num_failure(self): """Verify --limit X, where X is a negative integer, fails. @@ -82,24 +82,18 @@ class TestCellsShell(base.ShellTestCase): def test_cell_list_detail_success(self, mock_list): """Verify --detail argument successfully pass detail to Client.""" self.shell('cell-list -r 1 --detail') - mock_list.assert_called_once_with(detail=True) + mock_list.assert_called_once_with(detail=True, sort_dir='asc') @mock.patch('cratonclient.v1.cells.CellManager.list') @mock.patch('cratonclient.common.cliutils.print_list') def test_cell_list_fields_success(self, mock_printlist, mock_list): """Verify --fields argument successfully passed to Client.""" self.shell('cell-list -r 1 --fields id name') - mock_list.assert_called_once_with() + mock_list.assert_called_once_with(sort_dir='asc') mock_printlist.assert_called_once_with(mock.ANY, list({'id': 'ID', 'name': 'Name'})) - @mock.patch('cratonclient.v1.cells.CellManager.list') - def test_cell_list_detail_and_fields_specified(self, mock_list): - """Verify --fields ignored when --detail argument passed in.""" - self.shell('cell-list -r 1 --fields id name --detail') - mock_list.assert_called_once_with(detail=True) - @mock.patch('cratonclient.v1.cells.CellManager.list') def test_cell_list_sort_key_field_key_success(self, mock_list): """Verify --sort-key arguments successfully passed to Client.""" @@ -113,12 +107,6 @@ class TestCellsShell(base.ShellTestCase): self.shell, 'cell-list -r 1 --sort-key invalid') - @mock.patch('cratonclient.v1.cells.CellManager.list') - def test_cell_list_sort_dir_not_passed_without_sort_key(self, mock_list): - """Verify --sort-dir arg ignored without --sort-key.""" - self.shell('cell-list -r 1 --sort-dir desc') - mock_list.assert_called_once_with() - @mock.patch('cratonclient.v1.cells.CellManager.list') def test_cell_list_sort_dir_asc_success(self, mock_list): """Verify --sort-dir asc successfully passed to Client.""" @@ -135,9 +123,10 @@ class TestCellsShell(base.ShellTestCase): def test_cell_list_sort_dir_invalid_value(self): """Verify --sort-dir with invalid args, fails with Command Error.""" - self.assertRaises(exc.CommandError, - self.shell, - 'cell-list -r 1 --sort-key name --sort-dir invalid') + (_, error) = self.shell( + 'cell-list -r 1 --sort-key name --sort-dir invalid' + ) + self.assertIn("invalid choice: 'invalid'", error) def test_cell_create_missing_required_args(self): """Verify that missing required args results in error message.""" @@ -203,8 +192,7 @@ class TestCellsShell(base.ShellTestCase): id=1, name='mock_cell') cells_shell.do_cell_update(client, valid_input) - vars(valid_input).pop('region') - mock_update.assert_called_once_with(**vars(valid_input)) + mock_update.assert_called_once_with(1, name='mock_cell') @mock.patch('cratonclient.v1.cells.CellManager.update') def test_do_cell_update_ignores_unknown_fields(self, mock_update): @@ -221,9 +209,7 @@ class TestCellsShell(base.ShellTestCase): name='mock_cell', invalid=True) cells_shell.do_cell_update(client, invalid_input) - vars(invalid_input).pop('region') - vars(invalid_input).pop('invalid') - mock_update.assert_called_once_with(**vars(invalid_input)) + mock_update.assert_called_once_with(1, name='mock_cell') def test_cell_show_missing_required_args(self): """Verify that missing required args results in error message.""" diff --git a/cratonclient/tests/unit/shell/__init__.py b/cratonclient/tests/unit/shell/__init__.py new file mode 100644 index 0000000..77551fa --- /dev/null +++ b/cratonclient/tests/unit/shell/__init__.py @@ -0,0 +1 @@ +"""Unit tests for cratonclient.shell submodules.""" diff --git a/cratonclient/tests/unit/shell/v1/__init__.py b/cratonclient/tests/unit/shell/v1/__init__.py new file mode 100644 index 0000000..614116c --- /dev/null +++ b/cratonclient/tests/unit/shell/v1/__init__.py @@ -0,0 +1 @@ +"""Unit tests for cratonclient.shell.v1 submodules.""" diff --git a/cratonclient/tests/unit/shell/v1/test_cells_shell.py b/cratonclient/tests/unit/shell/v1/test_cells_shell.py new file mode 100644 index 0000000..b30e20c --- /dev/null +++ b/cratonclient/tests/unit/shell/v1/test_cells_shell.py @@ -0,0 +1,463 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Tests for the shell functions for the cells resource.""" +import argparse + +import mock + +from cratonclient import exceptions +from cratonclient.shell.v1 import cells_shell +from cratonclient.tests import base +from cratonclient.v1 import cells + + +class TestCells(base.TestCase): + """Base class for cells_shell commands.""" + + def setUp(self): + """Initialize test fixtures.""" + super(TestCells, self).setUp() + self.craton_client = mock.Mock() + self.inventory = mock.Mock() + self.craton_client.inventory.return_value = self.inventory + + def assertRaisesCommandErrorWith(self, func, args): + """Assert do_cell_create raises CommandError.""" + self.assertRaises( + exceptions.CommandError, + func, self.craton_client, args, + ) + + +class TestCellsPrintDict(TestCells): + """Base class for commands using print_dict.""" + + def setUp(self): + """Initialize test fixtures.""" + super(TestCellsPrintDict, self).setUp() + self.print_dict_patch = mock.patch( + 'cratonclient.common.cliutils.print_dict' + ) + self.print_dict = self.print_dict_patch.start() + + def tearDown(self): + """Clean-up test fixtures.""" + super(TestCellsPrintDict, self).tearDown() + self.print_dict_patch.stop() + + def assertNothingWasCalled(self): + """Assert inventory, list, and print_dict were not called.""" + self.assertFalse(self.craton_client.inventory.called) + self.assertFalse(self.inventory.cells.list.called) + self.assertFalse(self.print_dict.called) + + +class TestDoShellShow(TestCellsPrintDict): + """Unit tests for the cell show command.""" + + def test_simple_usage(self): + """Verify the behaviour of do_cell_show.""" + args = argparse.Namespace( + region=123, + id=456, + ) + + cells_shell.do_cell_show(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.get.assert_called_once_with(456) + self.print_dict.assert_called_once_with( + {f: mock.ANY for f in cells.CELL_FIELDS}, + wrap=72, + ) + + +class TestDoCellList(TestCells): + """Unit tests for the cell list command.""" + + def setUp(self): + """Initialize test fixtures.""" + super(TestDoCellList, self).setUp() + self.print_list_patch = mock.patch( + 'cratonclient.common.cliutils.print_list' + ) + self.print_list = self.print_list_patch.start() + + def tearDown(self): + """Clean-up test fixtures.""" + super(TestDoCellList, self).tearDown() + self.print_list_patch.stop() + + def assertNothingWasCalled(self): + """Assert inventory, list, and print_list were not called.""" + self.assertFalse(self.craton_client.inventory.called) + self.assertFalse(self.inventory.cells.list.called) + self.assertFalse(self.print_list.called) + + def test_with_defaults(self): + """Verify the behaviour of do_cell_list with mostly default values.""" + args = argparse.Namespace( + region=123, + detail=False, + limit=None, + sort_key=None, + sort_dir='asc', + fields=[], + ) + + cells_shell.do_cell_list(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.list.assert_called_once_with(sort_dir='asc') + self.assertTrue(self.print_list.called) + self.assertEqual(['id', 'name'], + sorted(self.print_list.call_args[0][-1])) + + def test_negative_limit(self): + """Ensure we raise an exception for negative limits.""" + args = argparse.Namespace( + region=123, + detail=False, + limit=-1, + sort_key=None, + sort_dir='asc', + fields=[], + ) + + self.assertRaisesCommandErrorWith(cells_shell.do_cell_list, args) + self.assertNothingWasCalled() + + def test_positive_limit(self): + """Verify that we pass positive limits to the call to list.""" + args = argparse.Namespace( + region=123, + detail=False, + limit=5, + sort_key=None, + sort_dir='asc', + fields=[], + ) + + cells_shell.do_cell_list(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.list.assert_called_once_with( + limit=5, + sort_dir='asc', + ) + self.assertTrue(self.print_list.called) + self.assertEqual(['id', 'name'], + sorted(self.print_list.call_args[0][-1])) + + def test_valid_sort_key(self): + """Verify that we pass on our sort key.""" + args = argparse.Namespace( + region=123, + detail=False, + limit=None, + sort_key='name', + sort_dir='asc', + fields=[], + ) + + cells_shell.do_cell_list(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.list.assert_called_once_with( + sort_dir='asc', + sort_key='name', + ) + self.assertTrue(self.print_list.called) + self.assertEqual(['id', 'name'], + sorted(self.print_list.call_args[0][-1])) + + def test_invalid_sort_key(self): + """Verify that do not we pass on our sort key.""" + args = argparse.Namespace( + region=123, + detail=False, + limit=None, + sort_key='fake-sort-key', + sort_dir='asc', + fields=[], + ) + + self.assertRaisesCommandErrorWith(cells_shell.do_cell_list, args) + self.assertNothingWasCalled() + + def test_detail(self): + """Verify the behaviour of specifying --detail.""" + args = argparse.Namespace( + region=123, + detail=True, + limit=None, + sort_key=None, + sort_dir='asc', + fields=[], + ) + + cells_shell.do_cell_list(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.list.assert_called_once_with( + sort_dir='asc', + detail=True, + ) + self.assertEqual(sorted(list(cells.CELL_FIELDS)), + sorted(self.print_list.call_args[0][-1])) + + def test_raises_exception_with_detail_and_fields(self): + """Verify that we fail when users specify --detail and --fields.""" + args = argparse.Namespace( + region=123, + detail=True, + limit=None, + sort_key=None, + sort_dir='asc', + fields=['id', 'name'], + ) + + self.assertRaisesCommandErrorWith(cells_shell.do_cell_list, args) + self.assertNothingWasCalled() + + def test_fields(self): + """Verify that we print out specific fields.""" + args = argparse.Namespace( + region=123, + detail=False, + limit=None, + sort_key=None, + sort_dir='asc', + fields=['id', 'name', 'note'], + ) + + cells_shell.do_cell_list(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.list.assert_called_once_with( + sort_dir='asc', + ) + self.assertEqual(['id', 'name', 'note'], + sorted(self.print_list.call_args[0][-1])) + + def test_invalid_fields(self): + """Verify that we error out with invalid fields.""" + args = argparse.Namespace( + region=123, + detail=False, + limit=None, + sort_key=None, + sort_dir='asc', + fields=['uuid', 'not-name', 'nate'], + ) + + self.assertRaisesCommandErrorWith(cells_shell.do_cell_list, args) + self.assertNothingWasCalled() + + +class TestDoCellCreate(TestCellsPrintDict): + """Unit tests for the cell create command.""" + + def test_create_without_note(self): + """Verify our parameters to cells.create.""" + args = argparse.Namespace( + name='New Cell', + region_id=123, + note=None, + ) + + cells_shell.do_cell_create(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.create.assert_called_once_with( + name='New Cell', + region_id=123, + ) + self.print_dict.assert_called_once_with(mock.ANY, wrap=72) + + def test_create_with_note(self): + """Verify that we include the note argument when present.""" + args = argparse.Namespace( + name='New Cell', + region_id=123, + note='This is a note', + ) + + cells_shell.do_cell_create(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.create.assert_called_once_with( + name='New Cell', + region_id=123, + note='This is a note', + ) + self.print_dict.assert_called_once_with(mock.ANY, wrap=72) + + +class TestDoCellUpdate(TestCellsPrintDict): + """Unit tests for the cell update command.""" + + def test_update_without_name_region_or_note_fails(self): + """Verify we raise a command error when there's nothing to update.""" + args = argparse.Namespace( + id=123, + region=345, + name=None, + region_id=None, + note=None, + ) + + self.assertRaisesCommandErrorWith(cells_shell.do_cell_update, args) + self.assertNothingWasCalled() + + def test_update_with_name(self): + """Verify we update with only the new name.""" + args = argparse.Namespace( + id=123, + region=345, + name='New name', + region_id=None, + note=None, + ) + + cells_shell.do_cell_update(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(345) + self.inventory.cells.update.assert_called_once_with( + 123, + name='New name', + ) + self.print_dict.assert_called_once_with(mock.ANY, wrap=72) + + def test_update_with_new_region(self): + """Verify we update with only the new region id.""" + args = argparse.Namespace( + id=123, + region=345, + name=None, + region_id=678, + note=None, + ) + + cells_shell.do_cell_update(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(345) + self.inventory.cells.update.assert_called_once_with( + 123, + region_id=678, + ) + self.print_dict.assert_called_once_with(mock.ANY, wrap=72) + + def test_update_with_new_note(self): + """Verify we update with only the new note text.""" + args = argparse.Namespace( + id=123, + region=345, + name=None, + region_id=None, + note='A new note', + ) + + cells_shell.do_cell_update(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(345) + self.inventory.cells.update.assert_called_once_with( + 123, + note='A new note', + ) + self.print_dict.assert_called_once_with(mock.ANY, wrap=72) + + def test_update_with_everything(self): + """Verify we update with everything.""" + args = argparse.Namespace( + id=123, + region=345, + name='A new name for a new region', + region_id=678, + note='A new note', + ) + + cells_shell.do_cell_update(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(345) + self.inventory.cells.update.assert_called_once_with( + 123, + name='A new name for a new region', + region_id=678, + note='A new note', + ) + self.print_dict.assert_called_once_with(mock.ANY, wrap=72) + + +class TestDoCellDelete(TestCells): + """Tests for the do_cell_delete command.""" + + def setUp(self): + """Initialize our print mock.""" + super(TestDoCellDelete, self).setUp() + self.print_func_mock = mock.patch( + 'cratonclient.shell.v1.cells_shell.print' + ) + self.print_func = self.print_func_mock.start() + + def tearDown(self): + """Clean up our print mock.""" + super(TestDoCellDelete, self).tearDown() + self.print_func_mock.stop() + + def test_successful(self): + """Verify the message we print when successful.""" + self.inventory.cells.delete.return_value = True + args = argparse.Namespace( + region=123, + id=456, + ) + + cells_shell.do_cell_delete(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.delete.assert_called_once_with(456) + self.print_func.assert_called_once_with( + 'Cell 456 was successfully deleted.' + ) + + def test_failed(self): + """Verify the message we print when deletion fails.""" + self.inventory.cells.delete.return_value = False + args = argparse.Namespace( + region=123, + id=456, + ) + + cells_shell.do_cell_delete(self.craton_client, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.delete.assert_called_once_with(456) + self.print_func.assert_called_once_with( + 'Cell 456 was not deleted.' + ) + + def test_failed_with_exception(self): + """Verify the message we print when deletion fails.""" + self.inventory.cells.delete.side_effect = exceptions.NotFound + args = argparse.Namespace( + region=123, + id=456, + ) + + self.assertRaisesCommandErrorWith(cells_shell.do_cell_delete, args) + + self.craton_client.inventory.assert_called_once_with(123) + self.inventory.cells.delete.assert_called_once_with(456) + self.assertFalse(self.print_func.called)