diff --git a/src/actions/actions.py b/src/actions/actions.py index 8da5fb0..bbdab6e 100755 --- a/src/actions/actions.py +++ b/src/actions/actions.py @@ -56,6 +56,7 @@ def set_temp_url_secret(*args): except Exception as e: ch_core.hookenv.action_fail('Failed to create keystone session ("{}")' .format(e)) + return os_cli = api_utils.OSClients(keystone_session) if os_cli.has_swift() is False: diff --git a/src/wheelhouse.txt b/src/wheelhouse.txt index be34836..db7c6bf 100644 --- a/src/wheelhouse.txt +++ b/src/wheelhouse.txt @@ -21,8 +21,9 @@ cryptography pyrsistent<0.18.0 iso8601<1.0.0 -importlib-metadata<3.0.0 +importlib-metadata==4.3.0 importlib-resources<3.0.0 +setuptools<60.9.0 git+https://github.com/openstack/charms.openstack.git#egg=charms.openstack diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index 4057fa1..f5e32f5 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -10,6 +10,10 @@ sys.modules['charmhelpers.core.decorators'] = ( charms_openstack.test_mocks.charmhelpers.core.decorators) +global snap +snap = mock.MagicMock() +sys.modules['charms.layer'] = snap + class _fake_decorator(object): diff --git a/unit_tests/test_actions.py b/unit_tests/test_actions.py new file mode 100644 index 0000000..bf48af0 --- /dev/null +++ b/unit_tests/test_actions.py @@ -0,0 +1,46 @@ +import src.actions.actions as actions +import unit_tests.test_utils +import reactive.ironic_handlers as handlers +import charm.openstack.ironic.api_utils as api_utils +import charmhelpers.core as ch_core +import charms.leadership as leadership + +import mock + + +class TestActions(unit_tests.test_utils.CharmTestCase): + + def setUp(self): + super(TestActions, self).setUp() + self.patches = [] + self.patch_all() + self.ironic_charm = mock.MagicMock() + self.patch_object(handlers.charm, 'provide_charm_instance', + new=mock.MagicMock()) + self.provide_charm_instance().__enter__.return_value = \ + self.ironic_charm + self.provide_charm_instance().__exit__.return_value = None + + def test_set_temp_url_secret_keystone_session_successful(self): + self.patch_object(ch_core.hookenv, 'action_fail') + self.patch_object(leadership, 'leader_get') + + actions.set_temp_url_secret() + + self.leader_get.assert_called_with('temp_url_secret') + + def test_set_temp_url_secret_keystone_session_exception(self): + self.patch_object(api_utils, 'create_keystone_session') + self.patch_object(ch_core.hookenv, 'action_fail') + self.patch_object(leadership, 'leader_get') + + def raise_exception(*args): + raise Exception("doh!") + + self.create_keystone_session.side_effect = raise_exception + + actions.set_temp_url_secret() + + self.action_fail.assert_called_with( + 'Failed to create keystone session ("doh!")') + self.leader_get.assert_not_called() diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py new file mode 100644 index 0000000..86607ce --- /dev/null +++ b/unit_tests/test_utils.py @@ -0,0 +1,99 @@ +from unittest import mock +import unittest + + +class CharmTestCase(unittest.TestCase): + + def setUp(self): + self._patches = {} + self._patches_start = {} + + def tearDown(self): + for k, v in self._patches.items(): + v.stop() + setattr(self, k, None) + self._patches = None + self._patches_start = None + + def _patch(self, method): + _m = unittest.mock.patch.object(self.obj, method) + mock = _m.start() + self.addCleanup(_m.stop) + return mock + + def patch_all(self): + for method in self.patches: + setattr(self, method, self._patch(method)) + + def patch_object(self, obj, attr, return_value=None, name=None, new=None, + **kwargs): + if name is None: + name = attr + if new is not None: + mocked = mock.patch.object(obj, attr, new=new, **kwargs) + else: + mocked = mock.patch.object(obj, attr, **kwargs) + self._patches[name] = mocked + started = mocked.start() + if new is None: + started.return_value = return_value + self._patches_start[name] = started + setattr(self, name, started) + + def patch(self, item, return_value=None, name=None, new=None, **kwargs): + if name is None: + raise RuntimeError("Must pass 'name' to .patch()") + if new is not None: + mocked = mock.patch(item, new=new, **kwargs) + else: + mocked = mock.patch(item, **kwargs) + self._patches[name] = mocked + started = mocked.start() + if new is None: + started.return_value = return_value + self._patches_start[name] = started + + +class TestConfig(object): + + def __init__(self): + self.config = {} + self.config_prev = {} + + def __call__(self, key=None): + if key: + return self.get(key) + else: + return self + + def previous(self, k): + return self.config_prev[k] if k in self.config_prev else self.config[k] + + def set_previous(self, k, v): + self.config_prev[k] = v + + def unset_previous(self, k): + if k in self.config_prev: + self.config_prev.pop(k) + + def changed(self, k): + if not self.config_prev: + return True + return self.get(k) != self.previous(k) + + def get(self, attr=None): + if not attr: + return self + try: + return self.config[attr] + except KeyError: + return None + + def get_all(self): + return self.config + + def set(self, attr, value): + self.config[attr] = value + + def __getitem__(self, k): + return self.get(k)