diff --git a/coriolisclient/cli/shell.py b/coriolisclient/cli/shell.py index 2b1f37c..c30c1ef 100644 --- a/coriolisclient/cli/shell.py +++ b/coriolisclient/cli/shell.py @@ -35,6 +35,7 @@ import six from coriolisclient import client +from coriolisclient import exceptions from coriolisclient import version @@ -100,7 +101,7 @@ def check_auth_arguments(self, args, api_version=None, raise_exc=False): successful = False if not successful and raise_exc: - raise Exception(msg) + raise exceptions.CoriolisException(msg) return successful @@ -157,7 +158,7 @@ def create_client(self, args): api_version = args.os_identity_api_version verify = args.os_cacert or not args.insecure if args.no_auth and args.os_auth_url: - raise Exception( + raise exceptions.CoriolisException( 'ERROR: argument --os-auth-url/-A: not allowed ' 'with argument --no-auth/-N' ) @@ -165,7 +166,7 @@ def create_client(self, args): if args.no_auth: if not all([args.endpoint, args.os_tenant_id or args.os_project_id]): - raise Exception( + raise exceptions.CoriolisException( 'ERROR: please specify --endpoint and ' '--os-project-id (or --os-tenant-id)') created_client = client.Client( @@ -177,7 +178,8 @@ def create_client(self, args): # Token-based authentication elif args.os_auth_token: if not args.os_auth_url: - raise Exception('ERROR: please specify --os-auth-url') + raise exceptions.CoriolisException( + 'ERROR: please specify --os-auth-url') token_kwargs = { 'auth_url': args.os_auth_url, 'token': args.os_auth_token @@ -210,7 +212,8 @@ def create_client(self, args): **endpoint_filter_kwargs ) else: - raise Exception('ERROR: please specify authentication credentials') + raise exceptions.CoriolisException( + 'ERROR: please specify authentication credentials') return created_client diff --git a/coriolisclient/cli/utils.py b/coriolisclient/cli/utils.py index b61ee28..730363f 100644 --- a/coriolisclient/cli/utils.py +++ b/coriolisclient/cli/utils.py @@ -175,7 +175,7 @@ def get_option_value_from_args(args, option_name, error_on_no_value=True): with file_arg as fin: raw_value = fin.read() - if not value and raw_value: + if raw_value: try: value = json.loads(raw_value) except ValueError as ex: diff --git a/coriolisclient/tests/cli/data/shell_check_auth_arguments.yml b/coriolisclient/tests/cli/data/shell_check_auth_arguments.yml new file mode 100644 index 0000000..79118cf --- /dev/null +++ b/coriolisclient/tests/cli/data/shell_check_auth_arguments.yml @@ -0,0 +1,129 @@ + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: '3' + raise_exc: False + expected_result: True + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: + raise_exc: False + expected_result: True + +- config: + args: + api_version: '3' + raise_exc: False + expected_result: False + +- config: + args: + os_project_id: "mock_os_project_id" + api_version: '3' + raise_exc: False + expected_result: True + +- config: + args: + os_project_name: "mock_os_project_name" + api_version: '3' + raise_exc: False + expected_result: False + +- config: + args: + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + api_version: '3' + raise_exc: False + expected_result: True + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + api_version: '3' + raise_exc: False + expected_result: False + +- config: + args: + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + api_version: + raise_exc: False + expected_result: True + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: + raise_exc: False + expected_result: False + +- config: + args: + api_version: + raise_exc: False + expected_result: False + +- config: + args: + api_version: + raise_exc: True + expected_result: + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: '2' + raise_exc: False + expected_result: True + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + api_version: '2' + raise_exc: False + expected_result: True + +- config: + args: + api_version: '2' + raise_exc: False + expected_result: False + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + api_version: '2' + raise_exc: False + expected_result: True + +- config: + args: + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + api_version: '2' + raise_exc: False + expected_result: False diff --git a/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml b/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml new file mode 100644 index 0000000..c36fcf3 --- /dev/null +++ b/coriolisclient/tests/cli/data/shell_create_keystone_auth.yml @@ -0,0 +1,133 @@ + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + insecure: True + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + project_id: "mock_os_project_id" + project_name: "mock_os_project_name" + project_domain_name: "mock_os_project_domain_name" + project_domain_id: "mock_os_project_domain_id" + verify: False + api_version: '3' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v3.Token' + +- config: + args: + os_project_id: "mock_os_project_id" + os_tenant_id: "mock_os_tenant_id" + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + project_id: "mock_os_project_id" + api_version: '3' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v3.Token' + +- config: + args: + os_project_id: "mock_os_project_id" + os_tenant_id: "mock_os_tenant_id" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + project_id: "mock_os_project_id" + api_version: '3' + auth_type: 'other' + auth_fun: 'keystoneauth1.identity.v3.Password' + + +- config: + args: + os_project_id: "mock_os_project_id" + os_tenant_id: "mock_os_tenant_id" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + project_id: "mock_os_project_id" + api_version: '3' + auth_type: + auth_fun: 'keystoneauth1.identity.v3.Password' + +- config: + args: + os_project_id: "mock_os_project_id" + os_project_name: "mock_os_project_name" + os_project_domain_name: "mock_os_project_domain_name" + os_project_domain_id: "mock_os_project_domain_id" + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v2.Token' + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + expected_kwargs: + auth_url: "mock_auth_url" + token: "mock_token" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: 'token' + auth_fun: 'keystoneauth1.identity.v2.Token' + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: 'other' + auth_fun: 'keystoneauth1.identity.v2.Password' + + +- config: + args: + os_tenant_id: "mock_os_tenant_id" + os_tenant_name: "mock_os_tenant_name" + kwargs: + auth_url: "mock_auth_url" + expected_kwargs: + auth_url: "mock_auth_url" + tenant_id: "mock_os_tenant_id" + tenant_name: "mock_os_tenant_name" + api_version: '2' + auth_type: + auth_fun: 'keystoneauth1.identity.v2.Password' diff --git a/coriolisclient/tests/cli/data/user_scripts.yml b/coriolisclient/tests/cli/data/user_scripts.yml new file mode 100644 index 0000000..7510465 --- /dev/null +++ b/coriolisclient/tests/cli/data/user_scripts.yml @@ -0,0 +1,2 @@ +"mock_script1" +"mock_script2" diff --git a/coriolisclient/tests/cli/test_formatter.py b/coriolisclient/tests/cli/test_formatter.py new file mode 100644 index 0000000..6b9830d --- /dev/null +++ b/coriolisclient/tests/cli/test_formatter.py @@ -0,0 +1,158 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +from unittest import mock + +from coriolisclient.cli import formatter +from coriolisclient.tests import test_base + + +class TestEntityFormattter(formatter.EntityFormatter): + + def __init__(self): + self.columns = [ + "column_1", + "column_2" + ] + + def _get_formatted_data(self, obj): + return obj + + +@ddt.ddt +class TestEntityFormattterTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Entity Formatter.""" + + def setUp(self): + super(TestEntityFormattterTestCase, self).setUp() + self.format = TestEntityFormattter() + + def test_get_sorted_list(self): + obj_list = ["obj2", "obj1"] + + result = self.format._get_sorted_list(obj_list) + + self.assertEqual( + obj_list, + result + ) + + def test_list_objects(self): + obj_list = ["obj2", "obj1"] + + (columns, data) = self.format.list_objects(obj_list) + + self.assertEqual( + ( + self.format.columns, + ["obj2", "obj1"] + ), + ( + columns, + list(data) + ) + ) + + def test_get_generic_data(self): + obj = mock.Mock() + + result = self.format._get_generic_data(obj) + + self.assertEqual( + obj, + result + ) + + def test_get_generic_columns(self): + result = self.format._get_generic_columns() + + self.assertEqual( + self.format.columns, + result + ) + + def test_get_formatted_entity(self): + obj = mock.Mock() + + (columns, data) = self.format.get_formatted_entity(obj) + + self.assertEqual( + (self.format.columns, obj), + (columns, data) + ) + + @ddt.data( + { + "current_value": 3, + "max_value": 9, + "percent_format": "{:.0f}%", + "expected_result": "33%" + }, + { + "current_value": 3, + "max_value": 9, + "percent_format": "{:.2f}%", + "expected_result": "33.33%" + }, + { + "current_value": 0, + "max_value": 9, + "percent_format": "{:.0f}%", + "expected_result": None + }, + { + "current_value": 3, + "max_value": None, + "percent_format": "{:.0f}%", + "expected_result": None + } + ) + def test_get_percent_string(self, data): + result = self.format._get_percent_string( + data["current_value"], + data["max_value"], + percent_format=data["percent_format"] + ) + + self.assertEqual( + data["expected_result"], + result + ) + + @mock.patch.object(formatter.EntityFormatter, '_get_percent_string') + def test_format_progress_update(self, mock_get_percent_string): + progress_update = { + "current_step": "mock_current_step", + "total_steps": "mock_total_steps", + "created_at": "mock_created_at", + "message": "mock_message", + } + mock_get_percent_string.return_value = "mock_percent_string" + + result = self.format._format_progress_update(progress_update) + + self.assertEqual( + "mock_created_at [mock_percent_string] mock_message", + result + ) + + @mock.patch.object(formatter.EntityFormatter, '_get_percent_string') + def test_format_progress_update_no_percent_string( + self, + mock_get_percent_string + ): + progress_update = { + "current_step": "mock_current_step", + "total_steps": "mock_total_steps", + "created_at": "mock_created_at", + "message": "mock_message", + } + mock_get_percent_string.return_value = None + + result = self.format._format_progress_update(progress_update) + + self.assertEqual( + "mock_created_at mock_message", + result + ) diff --git a/coriolisclient/tests/cli/test_shell.py b/coriolisclient/tests/cli/test_shell.py new file mode 100644 index 0000000..196808e --- /dev/null +++ b/coriolisclient/tests/cli/test_shell.py @@ -0,0 +1,438 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import ddt +import logging +import os +from unittest import mock + +from cliff import app +from keystoneauth1 import loading +from keystoneauth1 import session + +from coriolisclient.cli import shell +from coriolisclient import client +from coriolisclient import exceptions +from coriolisclient.tests import test_base + + +class CustomMock(mock.MagicMock): + def __getattr__(self, name): + return None + + +@ddt.ddt +class CoriolisTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Shell App.""" + + def setUp(self): + super(CoriolisTestCase, self).setUp() + self.coriolis = shell.Coriolis() + + @ddt.file_data('data/shell_check_auth_arguments.yml') + @ddt.unpack + def test_check_auth_arguments(self, config): + args = CustomMock() + if config.get("args"): + for key, value in config.get("args").items(): + setattr(args, key, value) + expected_result = config.get("expected_result") + + if expected_result is not None: + result = self.coriolis.check_auth_arguments( + args, + api_version=config.get("api_version", None), + raise_exc=config.get("raise_exc", False) + ) + + self.assertEqual( + expected_result, + result + ) + else: + self.assertRaises( + exceptions.CoriolisException, + self.coriolis.check_auth_arguments, + args, + api_version=config.get("api_version", None), + raise_exc=config.get("raise_exc", False) + ) + + @ddt.file_data('data/shell_create_keystone_auth.yml') + @ddt.unpack + @mock.patch.object(session, 'Session') + def test_create_keystone_session( + self, + mock_Session, + config + ): + args = CustomMock() + if config.get("args"): + for key, value in config.get("args").items(): + setattr(args, key, value) + kwargs = config.get("kwargs", {}) + expected_kwargs = config.get("expected_kwargs", {}) + mock_stderr = mock.Mock() + self.coriolis.stderr = mock_stderr + + auth_fun = config['auth_fun'] + with mock.patch(auth_fun) as mock_auth: + result = self.coriolis.create_keystone_session( + args, + api_version=config.get("api_version", None), + kwargs_dict=kwargs, + auth_type=config.get("auth_type", None), + verify=config.get("verify", True) + ) + + self.assertEqual( + mock_Session.return_value, + result + ) + mock_Session.assert_called_once_with( + auth=mock_auth.return_value, verify=config.get("verify", True)) + mock_auth.assert_called_once_with(**expected_kwargs) + + @ddt.data( + { + "args": { + "no_auth": "mock_no_auth", + "os_auth_url": "mock_os_auth_url" + } + }, + { + "args": { + "no_auth": "mock_no_auth", + "endpoint": "mock_endpoint" + } + }, + { + "args": { + "no_auth": "mock_no_auth", + "endpoint": "mock_endpoint", + "os_tenant_id": "mock_os_tenant_id" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "project_id": "mock_os_tenant_id", + } + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token" + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint", + "region_name": "mock_region_name", + "insecure": True + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token", + "verify": False + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint", + "region_name": "mock_region_name", + "insecure": True, + "os_cacert": "mock_os_cacert" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint", + "region_name": "mock_region_name" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token", + "verify": "mock_os_cacert" + }, + { + "args": { + "os_auth_token": "mock_os_auth_token" + } + }, + { + "args": { + "os_auth_token": "mock_os_auth_token", + "os_auth_url": "mock_os_auth_url", + "os_tenant_id": "mock_os_tenant_id", + "endpoint": "mock_endpoint" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "token": "mock_os_auth_token" + }, + "auth_type": "token" + }, + { + "args": { + "os_auth_url": "mock_os_auth_url", + "os_password": "mock_os_password", + "os_user_id": "mock_os_user_id", + "os_username": "mock_os_username", + "endpoint": "mock_endpoint" + }, + "expected_client_kwargs": { + "endpoint": "mock_endpoint" + }, + "expected_ks_session_kwargs": { + "auth_url": "mock_os_auth_url", + "password": "mock_os_password", + "user_id": "mock_os_user_id", + "username": "mock_os_username", + }, + "auth_type": "password" + }, + {} + ) + @mock.patch.object(shell.Coriolis, 'create_keystone_session') + @mock.patch.object(client, 'Client') + def test_create_client( + self, + data, + mock_Client, + mock_create_ks_session + ): + args = CustomMock() + args.os_identity_api_version = mock.sentinel.os_identity_api_version + for key, value in data.get("args", {}).items(): + setattr(args, key, value) + expected_client_kwargs = data.get("expected_client_kwargs") + expected_ks_session_kwargs = data.get("expected_ks_session_kwargs", {}) + + if expected_client_kwargs is not None: + result = self.coriolis.create_client(args) + self.assertEqual( + result, mock_Client.return_value) + if expected_ks_session_kwargs: + mock_Client.assert_called_once_with( + session=mock_create_ks_session.return_value, + **expected_client_kwargs, + verify=data.get("verify", True)) + mock_create_ks_session.assert_called_once_with( + args, + mock.sentinel.os_identity_api_version, + expected_ks_session_kwargs, + auth_type=data["auth_type"], + verify=data.get("verify", True)) + else: + mock_Client.assert_called_once_with( + **expected_client_kwargs, + verify=data.get("verify", True)) + else: + self.assertRaises( + exceptions.CoriolisException, + self.coriolis.create_client, + args + ) + + @ddt.data( + { + "args": { + "os_project_id": "mock_project_id", + "os_tenant_name": "mock_tenant_name" + }, + "api_version": 3, + "expected_kwargs": { + "tenant_name": "mock_tenant_name" + } + }, + { + "args": { + "os_project_id": "mock_project_id", + "os_tenant_name": "mock_tenant_name" + }, + "api_version": 2, + "expected_kwargs": { + "tenant_name": "mock_tenant_name" + } + }, + { + "args": { + "os_project_id": "mock_project_id", + "os_tenant_name": "mock_tenant_name" + }, + "api_version": None, + "expected_kwargs": { + "project_id": "mock_project_id" + } + }, + { + "args": {}, + "api_version": None, + "expected_kwargs": {} + } + ) + def test_build_kwargs_based_on_version(self, data): + args = CustomMock() + for key, value in data.get("args", {}).items(): + setattr(args, key, value) + api_version = data["api_version"] + + result = self.coriolis.build_kwargs_based_on_version( + args, api_version=api_version) + + self.assertEqual( + data["expected_kwargs"], + result + ) + + def test_get_endpoint_filter_kwargs( + self + ): + args = mock.Mock() + args.interface = mock.sentinel.interface + args.service_type = mock.sentinel.service_type + args.service_name = mock.sentinel.service_name + args.coriolis_api_version = mock.sentinel.coriolis_api_version + args.region_name = mock.sentinel.region_name + expected_result = { + "interface": mock.sentinel.interface, + "service_type": mock.sentinel.service_type, + "service_name": mock.sentinel.service_name, + "region_name": mock.sentinel.region_name, + "version": mock.sentinel.coriolis_api_version, + } + + result = self.coriolis._get_endpoint_filter_kwargs(args) + + self.assertEqual( + expected_result, + result + ) + + @mock.patch.object(shell.Coriolis, '_env') + @mock.patch.object(loading, 'register_session_argparse_arguments') + @mock.patch.object(app.App, 'build_option_parser') + def test_build_option_parser( + self, + mock_build_option_parser, + mock_register_session_argparse_arguments, + *_ + ): + result = self.coriolis.build_option_parser( + mock.sentinel.description, mock.sentinel.version) + + self.assertEqual( + mock_build_option_parser.return_value, + result + ) + mock_build_option_parser.assert_called_once_with( + mock.sentinel.description, mock.sentinel.version, None) + mock_register_session_argparse_arguments.assert_called_once_with( + mock_build_option_parser.return_value) + + def test_env(self): + os.environ["TEST_ENV"] = "test_value" + result = self.coriolis._env( + "TEST_ENV", mock.sentinel.default) + + self.assertEqual( + "test_value", + result + ) + + @mock.patch.object(shell.Coriolis, 'create_client') + def test_prepare_to_run_command(self, mock_create_client): + cmd = mock.Mock() + cmd.auth_required = True + self.coriolis.options = mock.sentinel.options + + self.coriolis.prepare_to_run_command(cmd) + + self.assertEqual( + mock_create_client.return_value, + self.coriolis.client_manager.coriolis + ) + mock_create_client.assert_called_once_with(mock.sentinel.options) + + @mock.patch.object(app.App, 'run') + def test_run(self, mock_run): + mock_stderr = mock.Mock() + mock_parser = mock.Mock() + self.coriolis.stderr = mock_stderr + self.coriolis.parser = mock_parser + + result = self.coriolis.run(None) + + self.assertEqual( + 1, + result + ) + mock_stderr.write.assert_called_once_with( + mock_parser.format_usage.return_value) + mock_run.assert_not_called() + + mock_stderr.reset_mock() + mock_parser.reset_mock() + + result = self.coriolis.run(mock.sentinel.argv) + + self.assertEqual( + mock_run.return_value, + result + ) + mock_parser.format_usage.assert_not_called() + + +class ShellTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Shell.""" + + def test_setup_logging(self): + shell._setup_logging() + self.assertEqual( + ( + logging.WARNING, + logging.ERROR + ), + ( + logging.getLogger("requests").level, + logging.getLogger("keystoneclient").level + ) + ) + + @mock.patch.object(shell.Coriolis, 'run') + def test_main( + self, + mock_run + ): + result = shell.main(mock.sentinel.argv) + + self.assertEqual( + mock_run.return_value, + result + ) + mock_run.assert_called_once_with(mock.sentinel.argv) diff --git a/coriolisclient/tests/cli/test_utils.py b/coriolisclient/tests/cli/test_utils.py new file mode 100644 index 0000000..61461fd --- /dev/null +++ b/coriolisclient/tests/cli/test_utils.py @@ -0,0 +1,325 @@ +# Copyright 2024 Cloudbase Solutions Srl +# All Rights Reserved. + +import argparse +import ddt +import os +from unittest import mock + +from coriolisclient.cli import utils +from coriolisclient.tests import test_base + + +@ddt.ddt +class UtilsTestCase(test_base.CoriolisBaseTestCase): + """Test suite for the Coriolis Utils.""" + + def test_add_storage_mappings_arguments_to_parser(self): + parser = argparse.ArgumentParser() + + utils.add_storage_mappings_arguments_to_parser(parser) + args = parser.parse_args( + ['--storage-backend-mapping', 'mock_source=mock_destination', + '--disk-storage-mapping', 'mock_disk_id=mock_destination', + '--default-storage-backend', 'mock_default_storage_backend']) + + self.assertEqual( + [{'disk_id': 'mock_disk_id', 'destination': 'mock_destination'}], + args.disk_storage_mappings + ) + self.assertEqual( + [{'source': 'mock_source', 'destination': 'mock_destination'}], + args.storage_backend_mappings + ) + self.assertEqual( + 'mock_default_storage_backend', + args.default_storage_backend + ) + + def test_get_storage_mappings_dict_from_args(self): + args = mock.Mock() + args.default_storage_backend = mock.sentinel.default_storage_backend + args.disk_storage_mappings = mock.sentinel.disk_storage_mappings + args.storage_backend_mappings = mock.sentinel.storage_backend_mappings + + result = utils.get_storage_mappings_dict_from_args(args) + + self.assertEqual( + { + "backend_mappings": mock.sentinel.storage_backend_mappings, + "default": mock.sentinel.default_storage_backend, + "disk_mappings": mock.sentinel.disk_storage_mappings + }, + result + ) + + def test_format_mapping(self): + mapping = { + "mapping1": "mock_mapping1", + "mapping2": "mock_mapping2", + "mapping3": "mock_mapping3" + } + + result = utils.format_mapping(mapping) + + self.assertEqual( + ( + "'mapping1'='mock_mapping1', " + "'mapping2'='mock_mapping2', " + "'mapping3'='mock_mapping3'" + ), + result + ) + + def test_parse_storage_mappings(self): + storage_mappings = { + "default": "mock_default", + "backend_mappings": [ + { + "source": "mock_source", + "destination": "mock_destination" + } + ], + "disk_mappings": [ + { + "disk_id": "mock_disk_id", + "destination": "mock_destination" + } + ] + } + + result = utils.parse_storage_mappings(storage_mappings) + + self.assertEqual( + ( + 'mock_default', + {'mock_source': 'mock_destination'}, + {'mock_disk_id': 'mock_destination'} + ), + result + ) + + def test_parse_storage_mappings_none(self): + result = utils.parse_storage_mappings(None) + + self.assertEqual( + (None, {}, {}), + result + ) + + def test_format_json_for_object_property(self): + obj = mock.Mock() + obj.prop_name = {"key1": "value1", "key2": "value2"} + + result = utils.format_json_for_object_property(obj, "prop_name") + + self.assertEqual( + '{\n "key1": "value1",\n "key2": "value2"\n}', + result + ) + + def test_format_json_for_object_property_to_dict(self): + obj = mock.Mock() + obj.prop_name.to_dict.return_value = \ + {"key1": "value1", "key2": "value2"} + + result = utils.format_json_for_object_property(obj, "prop_name") + + self.assertEqual( + '{\n "key1": "value1",\n "key2": "value2"\n}', + result + ) + + def test_format_json_for_object_property_none(self): + obj = mock.Mock() + obj.prop_name = None + + result = utils.format_json_for_object_property(obj, "prop_name") + + self.assertEqual( + '{}', + result + ) + + def test_validate_uuid_string(self): + result = utils.validate_uuid_string( + "12345678-9ABC-DEF1-2345-6789abcdef12") + + self.assertEqual( + True, + result + ) + + result = utils.validate_uuid_string( + "123456789ABCDEF") + + self.assertEqual( + False, + result + ) + + @mock.patch.object(argparse, 'FileType') + def test_add_args_for_json_option_to_parser(self, mock_file_type): + parser = argparse.ArgumentParser() + + utils.add_args_for_json_option_to_parser( + parser, "option_name") + + args = parser.parse_args( + ['--option-name', 'mock_option']) + + args_file = parser.parse_args( + ['--option-name-file', 'mock_option_file']) + + self.assertEqual( + ('mock_option', mock_file_type.return_value.return_value), + (args.option_name, args_file.option_name_file) + ) + + def test_get_option_value_from_args(self): + args = mock.MagicMock() + args.option_name = '{"option": "raw_value"}' + args.option_name_file.__enter__.return_value.read.return_value = \ + '{"option": "file_value"}' + + result = utils.get_option_value_from_args(args, "option-name") + + self.assertEqual( + {'option': 'raw_value'}, + result + ) + + args.option_name = None + + result = utils.get_option_value_from_args(args, "option-name") + + self.assertEqual( + {'option': 'file_value'}, + result + ) + + def test_get_option_value_from_args_no_value(self): + args = mock.Mock() + args.option_name = None + args.option_name_file = None + + result = utils.get_option_value_from_args( + args, "option-name", error_on_no_value=False) + + self.assertEqual( + None, + result + ) + + self.assertRaises( + ValueError, + utils.get_option_value_from_args, + args, + "option-name" + ) + + def test_get_option_value_from_args_json_value_error(self): + args = mock.Mock() + args.option_name = "invalid" + args.option_name_file = None + + self.assertRaises( + ValueError, + utils.get_option_value_from_args, + args, + "option-name" + ) + + @ddt.data( + { + "global_scripts": None, + "instance_scripts": None, + "expected_result": + { + "global": {}, + "instances": {} + } + }, + { + "global_scripts": ["linux="], + "instance_scripts": ["instance_1="], + "expected_result": + { + "global": {"linux": None}, + "instances": {"instance_1": None} + } + }, + { + "global_scripts": ["linux script"], + "instance_scripts": ["linux script"], + "expected_result": + { + "global": {}, + "instances": {} + } + }, + { + "global_scripts": ["invalid_os=scrips"], + "instance_scripts": None, + "expected_result": None + }, + { + "global_scripts": ["linux='invalid/file/path'"], + "instance_scripts": None, + "expected_result": None + }, + { + "global_scripts": None, + "instance_scripts": ["linux='invalid/file/path'"], + "expected_result": None + }, + ) + def test_compose_user_scripts(self, data): + global_scripts = data["global_scripts"] + instance_scripts = data["instance_scripts"] + expected_result = data["expected_result"] + + if expected_result: + result = utils.compose_user_scripts( + global_scripts, instance_scripts) + + self.assertEqual( + expected_result, + result + ) + else: + self.assertRaises( + ValueError, + utils.compose_user_scripts, + global_scripts, + instance_scripts + ) + + def test_compose_user_scripts_from_file(self): + script_path = os.path.dirname(os.path.realpath(__file__)) + script_path = os.path.join(script_path, 'data/user_scripts.yml') + global_scripts = ["linux=%s" % script_path] + instance_scripts = ["linux=%s" % script_path] + + result = utils.compose_user_scripts(global_scripts, instance_scripts) + + self.assertEqual( + { + 'global': {'linux': '"mock_script1"\n"mock_script2"\n'}, + 'instances': {'linux': '"mock_script1"\n"mock_script2"\n'} + }, + result + ) + + def test_add_minion_pool_args_to_parser(self): + parser = argparse.ArgumentParser() + + utils.add_minion_pool_args_to_parser(parser) + args = parser.parse_args( + ['--osmorphing-minion-pool-mapping', + 'mock_instance_id=mock_pool_id']) + + self.assertEqual( + [{'instance_id': 'mock_instance_id', 'pool_id': 'mock_pool_id'}], + args.instance_osmorphing_minion_pool_mappings + )