diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/netapp/azure/tests/unit | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/netapp/azure/tests/unit')
11 files changed, 1461 insertions, 0 deletions
diff --git a/ansible_collections/netapp/azure/tests/unit/compat/__init__.py b/ansible_collections/netapp/azure/tests/unit/compat/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/compat/__init__.py diff --git a/ansible_collections/netapp/azure/tests/unit/compat/builtins.py b/ansible_collections/netapp/azure/tests/unit/compat/builtins.py new file mode 100644 index 000000000..f60ee6782 --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/compat/builtins.py @@ -0,0 +1,33 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# +# Compat for python2.7 +# + +# One unittest needs to import builtins via __import__() so we need to have +# the string that represents it +try: + import __builtin__ +except ImportError: + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' diff --git a/ansible_collections/netapp/azure/tests/unit/compat/mock.py b/ansible_collections/netapp/azure/tests/unit/compat/mock.py new file mode 100644 index 000000000..0972cd2e8 --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/compat/mock.py @@ -0,0 +1,122 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python3.x's unittest.mock module +''' +import sys + +# Python 2.7 + +# Note: Could use the pypi mock library on python3.x as well as python2.x. It +# is the same as the python3 stdlib mock library + +try: + # Allow wildcard import because we really do want to import all of mock's + # symbols into this compat shim + # pylint: disable=wildcard-import,unused-wildcard-import + from unittest.mock import * +except ImportError: + # Python 2 + # pylint: disable=wildcard-import,unused-wildcard-import + try: + from mock import * + except ImportError: + print('You need the mock library installed on python2.x to run tests') + + +# Prior to 3.4.4, mock_open cannot handle binary read_data +if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): + file_spec = None + + def _iterate_read_data(read_data): + # Helper for mock_open: + # Retrieve lines from read_data via a generator so that separate calls to + # readline, read, and readlines are properly interleaved + sep = b'\n' if isinstance(read_data, bytes) else '\n' + data_as_list = [l + sep for l in read_data.split(sep)] + + if data_as_list[-1] == sep: + # If the last line ended in a newline, the list comprehension will have an + # extra entry that's just a newline. Remove this. + data_as_list = data_as_list[:-1] + else: + # If there wasn't an extra newline by itself, then the file being + # emulated doesn't have a newline to end the last line remove the + # newline that our naive format() added + data_as_list[-1] = data_as_list[-1][:-1] + + for line in data_as_list: + yield line + + def mock_open(mock=None, read_data=''): + """ + A helper function to create a mock to replace the use of `open`. It works + for `open` called directly or used as a context manager. + + The `mock` argument is the mock object to configure. If `None` (the + default) then a `MagicMock` will be created for you, with the API limited + to methods or attributes available on standard file handles. + + `read_data` is a string for the `read` methoddline`, and `readlines` of the + file handle to return. This is an empty string by default. + """ + def _readlines_side_effect(*args, **kwargs): + if handle.readlines.return_value is not None: + return handle.readlines.return_value + return list(_data) + + def _read_side_effect(*args, **kwargs): + if handle.read.return_value is not None: + return handle.read.return_value + return type(read_data)().join(_data) + + def _readline_side_effect(): + if handle.readline.return_value is not None: + while True: + yield handle.readline.return_value + for line in _data: + yield line + + global file_spec + if file_spec is None: + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + + if mock is None: + mock = MagicMock(name='open', spec=open) + + handle = MagicMock(spec=file_spec) + handle.__enter__.return_value = handle + + _data = _iterate_read_data(read_data) + + handle.write.return_value = None + handle.read.return_value = None + handle.readline.return_value = None + handle.readlines.return_value = None + + handle.read.side_effect = _read_side_effect + handle.readline.side_effect = _readline_side_effect() + handle.readlines.side_effect = _readlines_side_effect + + mock.return_value = handle + return mock diff --git a/ansible_collections/netapp/azure/tests/unit/compat/unittest.py b/ansible_collections/netapp/azure/tests/unit/compat/unittest.py new file mode 100644 index 000000000..73a20cf8c --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/compat/unittest.py @@ -0,0 +1,44 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python2.7's unittest module +''' + +import sys + +import pytest + +# Allow wildcard import because we really do want to import all of +# unittests's symbols into this compat shim +# pylint: disable=wildcard-import,unused-wildcard-import +if sys.version_info < (2, 7): + try: + # Need unittest2 on python2.6 + from unittest2 import * + except ImportError: + print('You need unittest2 installed on python2.6.x to run tests') + + class TestCase: + """ skip everything """ + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as unittest2 may not be available') +else: + from unittest import * diff --git a/ansible_collections/netapp/azure/tests/unit/plugins/module_utils/test_netapp_module.py b/ansible_collections/netapp/azure/tests/unit/plugins/module_utils/test_netapp_module.py new file mode 100644 index 000000000..fb83c464e --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/plugins/module_utils/test_netapp_module.py @@ -0,0 +1,149 @@ +# Copyright (c) 2018 NetApp +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for module_utils netapp_module.py ''' +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.netapp.azure.tests.unit.compat import unittest +from ansible_collections.netapp.azure.plugins.module_utils.netapp_module import NetAppModule as na_helper + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def test_get_cd_action_create(self): + ''' validate cd_action for create ''' + current = None + desired = {'state': 'present'} + my_obj = na_helper() + result = my_obj.get_cd_action(current, desired) + assert result == 'create' + + def test_get_cd_action_delete(self): + ''' validate cd_action for delete ''' + current = {'state': 'absent'} + desired = {'state': 'absent'} + my_obj = na_helper() + result = my_obj.get_cd_action(current, desired) + assert result == 'delete' + + def test_get_cd_action(self): + ''' validate cd_action for returning None ''' + current = None + desired = {'state': 'absent'} + my_obj = na_helper() + result = my_obj.get_cd_action(current, desired) + assert result is None + + def test_get_modified_attributes_for_no_data(self): + ''' validate modified attributes when current is None ''' + current = None + desired = {'name': 'test'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired) + assert result == {} + + def test_get_modified_attributes(self): + ''' validate modified attributes ''' + current = {'name': ['test', 'abcd', 'xyz', 'pqr'], 'state': 'present'} + desired = {'name': ['abcd', 'abc', 'xyz', 'pqr'], 'state': 'absent'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired) + assert result == desired + + def test_get_modified_attributes_for_intersecting_mixed_list(self): + ''' validate modified attributes for list diff ''' + current = {'name': [2, 'four', 'six', 8]} + desired = {'name': ['a', 8, 'ab', 'four', 'abcd']} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'name': ['a', 'ab', 'abcd']} + + def test_get_modified_attributes_for_intersecting_list(self): + ''' validate modified attributes for list diff ''' + current = {'name': ['two', 'four', 'six', 'eight']} + desired = {'name': ['a', 'six', 'ab', 'four', 'abc']} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'name': ['a', 'ab', 'abc']} + + def test_get_modified_attributes_for_nonintersecting_list(self): + ''' validate modified attributes for list diff ''' + current = {'name': ['two', 'four', 'six', 'eight']} + desired = {'name': ['a', 'ab', 'abd']} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'name': ['a', 'ab', 'abd']} + + def test_get_modified_attributes_for_list_of_dicts_no_data(self): + ''' validate modified attributes for list diff ''' + current = None + desired = {'address_blocks': [{'start': '10.20.10.40', 'size': 5}]} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {} + + def test_get_modified_attributes_for_intersecting_list_of_dicts(self): + ''' validate modified attributes for list diff ''' + current = {'address_blocks': [{'start': '10.10.10.23', 'size': 5}, {'start': '10.10.10.30', 'size': 5}]} + desired = {'address_blocks': [{'start': '10.10.10.23', 'size': 5}, {'start': '10.10.10.30', 'size': 5}, {'start': '10.20.10.40', 'size': 5}]} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'address_blocks': [{'start': '10.20.10.40', 'size': 5}]} + + def test_get_modified_attributes_for_nonintersecting_list_of_dicts(self): + ''' validate modified attributes for list diff ''' + current = {'address_blocks': [{'start': '10.10.10.23', 'size': 5}, {'start': '10.10.10.30', 'size': 5}]} + desired = {'address_blocks': [{'start': '10.20.10.23', 'size': 5}, {'start': '10.20.10.30', 'size': 5}, {'start': '10.20.10.40', 'size': 5}]} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'address_blocks': [{'start': '10.20.10.23', 'size': 5}, {'start': '10.20.10.30', 'size': 5}, {'start': '10.20.10.40', 'size': 5}]} + + def test_get_modified_attributes_for_list_diff(self): + ''' validate modified attributes for list diff ''' + current = {'name': ['test', 'abcd'], 'state': 'present'} + desired = {'name': ['abcd', 'abc'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'name': ['abc']} + + def test_get_modified_attributes_for_no_change(self): + ''' validate modified attributes for same data in current and desired ''' + current = {'name': 'test'} + desired = {'name': 'test'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired) + assert result == {} + + def test_is_rename_action_for_empty_input(self): + ''' validate rename action for input None ''' + source = None + target = None + my_obj = na_helper() + result = my_obj.is_rename_action(source, target) + assert result == source + + def test_is_rename_action_for_no_source(self): + ''' validate rename action when source is None ''' + source = None + target = 'test2' + my_obj = na_helper() + result = my_obj.is_rename_action(source, target) + assert result is False + + def test_is_rename_action_for_no_target(self): + ''' validate rename action when target is None ''' + source = 'test2' + target = None + my_obj = na_helper() + result = my_obj.is_rename_action(source, target) + assert result is True + + def test_is_rename_action(self): + ''' validate rename action ''' + source = 'test' + target = 'test2' + my_obj = na_helper() + result = my_obj.is_rename_action(source, target) + assert result is False diff --git a/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_account.py b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_account.py new file mode 100644 index 000000000..0d140b4a0 --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_account.py @@ -0,0 +1,173 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: azure_rm_netapp_account''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import sys + +import pytest +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not be available') + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.azure.tests.unit.compat import unittest +from ansible_collections.netapp.azure.tests.unit.compat.mock import patch, Mock + +HAS_AZURE_RMNETAPP_IMPORT = True +try: + # At this point, python believes the module is already loaded, so the import inside azure_rm_netapp_volume will be skipped. + from ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_account \ + import AzureRMNetAppAccount as account_module +except ImportError: + HAS_AZURE_RMNETAPP_IMPORT = False + +HAS_AZURE_CLOUD_ERROR_IMPORT = True +try: + from msrestazure.azure_exceptions import CloudError +except ImportError: + HAS_AZURE_CLOUD_ERROR_IMPORT = False + +if not HAS_AZURE_CLOUD_ERROR_IMPORT and sys.version_info < (3, 5): + pytestmark = pytest.mark.skip('skipping as missing required azure_exceptions on 2.6 and 2.7') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockAzureClient(object): + ''' mock server connection to ONTAP host ''' + def __init__(self): + ''' save arguments ''' + self.valid_accounts = ['test1', 'test2'] + + def get(self, resource_group, account_name): # pylint: disable=unused-argument + if account_name not in self.valid_accounts: + invalid = Response() + invalid.status_code = 404 + raise CloudError(response=invalid) + return Mock(name=account_name) + + def create_or_update(self, body, resource_group, account_name): # pylint: disable=unused-argument,no-self-use + return None + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.netapp_client = Mock() + self.netapp_client.accounts = MockAzureClient() + self._netapp_client = None + + def set_default_args(self): + resource_group = 'azure' + name = 'test1' + location = 'abc' + return dict({ + 'resource_group': resource_group, + 'name': name, + 'location': location + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + account_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_get_called_valid_account(self, client_f): + set_module_args(self.set_default_args()) + client_f.return_value = Mock() + client_f.side_effect = Mock() + my_obj = account_module() + my_obj.netapp_client.accounts = self.netapp_client.accounts + assert my_obj.get_azure_netapp_account() is not None + + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_get_called_non_existing_account(self, client_f): + data = self.set_default_args() + data['name'] = 'invalid' + set_module_args(data) + client_f.return_value = Mock() + client_f.side_effect = Mock() + my_obj = account_module() + my_obj.netapp_client.accounts = self.netapp_client.accounts + assert my_obj.get_azure_netapp_account() is None + + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_account.AzureRMNetAppAccount.get_azure_netapp_account') + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_account.AzureRMNetAppAccount.create_azure_netapp_account') + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_create_called(self, client_f, mock_create, mock_get): + data = dict(self.set_default_args()) + data['name'] = 'create' + data['tags'] = {'ttt': 'tesssttt', 'abc': 'xyz'} + set_module_args(data) + mock_get.return_value = None + client_f.return_value = Mock() + client_f.side_effect = Mock() + my_obj = account_module() + my_obj.netapp_client.accounts = self.netapp_client.accounts + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + mock_create.assert_called_with() + + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_account.AzureRMNetAppAccount.get_azure_netapp_account') + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_account.AzureRMNetAppAccount.delete_azure_netapp_account') + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_delete_called(self, client_f, mock_delete, mock_get): + data = dict(self.set_default_args()) + data['state'] = 'absent' + set_module_args(data) + mock_get.return_value = Mock() + client_f.return_value = Mock() + client_f.side_effect = Mock() + my_obj = account_module() + my_obj.netapp_client.accounts = self.netapp_client.accounts + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + mock_delete.assert_called_with() diff --git a/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_capacity_pool.py b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_capacity_pool.py new file mode 100644 index 000000000..91c8eefd6 --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_capacity_pool.py @@ -0,0 +1,197 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: azure_rm_netapp_capacity_pool''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import sys + +import pytest +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not be available') + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.azure.tests.unit.compat import unittest +from ansible_collections.netapp.azure.tests.unit.compat.mock import patch, Mock + +HAS_AZURE_RMNETAPP_IMPORT = True +try: + # At this point, python believes the module is already loaded, so the import inside azure_rm_netapp_volume will be skipped. + from ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_capacity_pool \ + import AzureRMNetAppCapacityPool as capacity_pool_module +except ImportError: + HAS_AZURE_RMNETAPP_IMPORT = False + +HAS_AZURE_CLOUD_ERROR_IMPORT = True +try: + from msrestazure.azure_exceptions import CloudError +except ImportError: + HAS_AZURE_CLOUD_ERROR_IMPORT = False + +if not HAS_AZURE_CLOUD_ERROR_IMPORT and sys.version_info < (3, 5): + pytestmark = pytest.mark.skip('skipping as missing required azure_exceptions on 2.6 and 2.7') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockAzureClient(object): + ''' mock server connection to ONTAP host ''' + def __init__(self): + ''' save arguments ''' + self.valid_pools = ['test1', 'test2'] + + def get(self, resource_group, account_name, pool_name): # pylint: disable=unused-argument + if pool_name not in self.valid_pools: + invalid = Response() + invalid.status_code = 404 + raise CloudError(response=invalid) + else: + return Mock(name=pool_name) + + def create_or_update(self, body, resource_group, account_name, pool_name): # pylint: disable=unused-argument + return None + + def update(self, body, resource_group, account_name, pool_name): # pylint: disable=unused-argument + return None + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.netapp_client = Mock() + self.netapp_client.pools = MockAzureClient() + self._netapp_client = None + + def set_default_args(self): + resource_group = 'azure' + account_name = 'azure' + name = 'test1' + location = 'abc' + size = 1 + service_level = 'Standard' + return dict({ + 'resource_group': resource_group, + 'account_name': account_name, + 'name': name, + 'location': location, + 'size': size, + 'service_level': service_level + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + capacity_pool_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_get_called_valid_capacity_pool(self, client_f): + set_module_args(self.set_default_args()) + client_f.return_value = Mock() + my_obj = capacity_pool_module() + my_obj.netapp_client.pools = self.netapp_client.pools + assert my_obj.get_azure_netapp_capacity_pool() is not None + + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_get_called_non_existing_capacity_pool(self, client_f): + data = self.set_default_args() + data['name'] = 'invalid' + set_module_args(data) + client_f.return_value = Mock() + my_obj = capacity_pool_module() + my_obj.netapp_client.pools = self.netapp_client.pools + assert my_obj.get_azure_netapp_capacity_pool() is None + + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_capacity_pool.AzureRMNetAppCapacityPool.get_azure_netapp_capacity_pool') + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_capacity_pool.AzureRMNetAppCapacityPool.create_azure_netapp_capacity_pool') + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_create_called(self, client_f, mock_create, mock_get): + data = dict(self.set_default_args()) + data['name'] = 'create' + set_module_args(data) + mock_get.return_value = None + client_f.return_value = Mock() + my_obj = capacity_pool_module() + my_obj.netapp_client.pools = self.netapp_client.pools + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + mock_create.assert_called_with() + + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_capacity_pool.AzureRMNetAppCapacityPool.get_azure_netapp_capacity_pool') + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_capacity_pool.AzureRMNetAppCapacityPool.create_azure_netapp_capacity_pool') + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_modify_called(self, client_f, mock_modify, mock_get): + data = dict(self.set_default_args()) + data['name'] = 'create' + data['size'] = 3 + set_module_args(data) + mock_get.return_value = None + client_f.return_value = Mock() + my_obj = capacity_pool_module() + my_obj.netapp_client.pools = self.netapp_client.pools + with pytest.raises(AnsibleExitJson) as exc: + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + mock_modify.assert_called_with() + + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_capacity_pool.AzureRMNetAppCapacityPool.get_azure_netapp_capacity_pool') + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_capacity_pool.AzureRMNetAppCapacityPool.delete_azure_netapp_capacity_pool') + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_delete_called(self, client_f, mock_delete, mock_get): + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + mock_get.return_value = Mock() + client_f.return_value = Mock() + my_obj = capacity_pool_module() + my_obj.netapp_client.pools = self.netapp_client.pools + with pytest.raises(AnsibleExitJson) as exc: + data['state'] = 'absent' + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + mock_delete.assert_called_with() diff --git a/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_snapshot.py b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_snapshot.py new file mode 100644 index 000000000..0415a4039 --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_snapshot.py @@ -0,0 +1,165 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: azure_rm_netapp_snapshot''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import sys + +import pytest +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not be available') + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.azure.tests.unit.compat import unittest +from ansible_collections.netapp.azure.tests.unit.compat.mock import patch, Mock + +HAS_AZURE_RMNETAPP_IMPORT = True +try: + # At this point, python believes the module is already loaded, so the import inside azure_rm_netapp_volume will be skipped. + from ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_snapshot \ + import AzureRMNetAppSnapshot as snapshot_module +except ImportError: + HAS_AZURE_RMNETAPP_IMPORT = False + +HAS_AZURE_CLOUD_ERROR_IMPORT = True +try: + from msrestazure.azure_exceptions import CloudError +except ImportError: + HAS_AZURE_CLOUD_ERROR_IMPORT = False + +if not HAS_AZURE_CLOUD_ERROR_IMPORT and sys.version_info < (3, 5): + pytestmark = pytest.mark.skip('skipping as missing required azure_exceptions on 2.6 and 2.7') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockAzureClient(object): + ''' mock server connection to ONTAP host ''' + def __init__(self): + ''' save arguments ''' + self.valid_snapshots = ['test1', 'test2'] + + def get(self, resource_group, account_name, pool_name, volume_name, snapshot_name): # pylint: disable=unused-argument + if snapshot_name not in self.valid_snapshots: + invalid = Response() + invalid.status_code = 404 + raise CloudError(response=invalid) + else: + return Mock(name=snapshot_name) + + def create(self, body, resource_group, account_name, pool_name, volume_name, snapshot_name): # pylint: disable=unused-argument + return None + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.netapp_client = Mock() + self.netapp_client.pools = MockAzureClient() + self._netapp_client = None + + def set_default_args(self): + resource_group = 'azure' + account_name = 'azure' + pool_name = 'azure' + volume_name = 'azure' + name = 'test1' + location = 'abc' + return dict({ + 'resource_group': resource_group, + 'account_name': account_name, + 'pool_name': pool_name, + 'volume_name': volume_name, + 'name': name, + 'location': location + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + snapshot_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + def test_ensure_get_called_valid_snapshot(self, client_f): + set_module_args(self.set_default_args()) + client_f.return_value = Mock() + my_obj = snapshot_module() + my_obj.netapp_client.snapshots = self.netapp_client.snapshots + assert my_obj.get_azure_netapp_snapshot() is not None + + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_snapshot.AzureRMNetAppSnapshot.get_azure_netapp_snapshot') + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_snapshot.AzureRMNetAppSnapshot.create_azure_netapp_snapshot') + def test_ensure_create_called(self, mock_create, mock_get, client_f): + data = dict(self.set_default_args()) + data['name'] = 'create' + set_module_args(data) + mock_get.return_value = None + client_f.return_value = Mock() + my_obj = snapshot_module() + my_obj.netapp_client.snapshots = self.netapp_client.snapshots + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + mock_create.assert_called_with() + + @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_snapshot.AzureRMNetAppSnapshot.get_azure_netapp_snapshot') + @patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_snapshot.AzureRMNetAppSnapshot.delete_azure_netapp_snapshot') + def test_ensure_delete_called(self, mock_delete, mock_get, client_f): + data = dict(self.set_default_args()) + data['state'] = 'absent' + set_module_args(data) + client_f.return_value = Mock() + mock_get.return_value = Mock() + my_obj = snapshot_module() + my_obj.netapp_client.snapshots = self.netapp_client.snapshots + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + mock_delete.assert_called_with() diff --git a/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_volume.py b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_volume.py new file mode 100644 index 000000000..83c7f812e --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_volume.py @@ -0,0 +1,501 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: azure_rm_netapp_volume''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import sys + +import pytest +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not be available') + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.azure.tests.unit.compat.mock import patch, Mock + +HAS_AZURE_RMNETAPP_IMPORT = True +try: + # At this point, python believes the module is already loaded, so the import inside azure_rm_netapp_volume will be skipped. + from ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume \ + import AzureRMNetAppVolume as volume_module +except ImportError: + HAS_AZURE_RMNETAPP_IMPORT = False + +HAS_AZURE_CLOUD_ERROR_IMPORT = True +try: + from msrestazure.azure_exceptions import CloudError +except ImportError: + HAS_AZURE_CLOUD_ERROR_IMPORT = False + +if not HAS_AZURE_CLOUD_ERROR_IMPORT and sys.version_info < (3, 5): + pytestmark = pytest.mark.skip('skipping as missing required azure_exceptions on 2.6 and 2.7') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockAzureClient(object): + ''' mock server connection to ONTAP host ''' + def __init__(self): + ''' save arguments ''' + self.valid_volumes = ['test1', 'test2'] + + def get(self, resource_group, account_name, pool_name, volume_name): # pylint: disable=unused-argument + if volume_name in self.valid_volumes: + return Mock(name=volume_name, + subnet_id='/resid/whatever/subnet_name', + mount_targets=[Mock(ip_address='1.2.3.4')] + ) + + invalid = Response() + invalid.status_code = 404 + raise CloudError(response=invalid) + + def create_or_update(self, body, resource_group, account_name, pool_name, volume_name): # pylint: disable=unused-argument + return None + + def begin_create_or_update(self, body, resource_group_name, account_name, pool_name, volume_name): # pylint: disable=unused-argument + return Mock(done=Mock(side_effect=[False, True])) + + def begin_update(self, body, resource_group_name, account_name, pool_name, volume_name): # pylint: disable=unused-argument + return Mock(done=Mock(side_effect=[False, True])) + + def begin_delete(self, resource_group_name, account_name, pool_name, volume_name): # pylint: disable=unused-argument + return Mock(done=Mock(side_effect=[False, True])) + + +class MockAzureClientRaise(MockAzureClient): + ''' mock server connection to ONTAP host ''' + response = Mock(status_code=400, context=None, headers=[], text=lambda: 'Forced exception') + + def begin_create_or_update(self, body, resource_group_name, account_name, pool_name, volume_name): # pylint: disable=unused-argument + raise CloudError(MockAzureClientRaise.response) + + def begin_update(self, body, resource_group_name, account_name, pool_name, volume_name): # pylint: disable=unused-argument + raise CloudError(MockAzureClientRaise.response) + + def begin_delete(self, resource_group_name, account_name, pool_name, volume_name): # pylint: disable=unused-argument + raise CloudError(MockAzureClientRaise.response) + + +# using pytest natively, without unittest.TestCase +@pytest.fixture(name="patch_ansible") +def fixture_patch_ansible(): + with patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) as mocks: + yield mocks + + +def set_default_args(): + resource_group = 'azure' + account_name = 'azure' + pool_name = 'azure' + name = 'test1' + location = 'abc' + file_path = 'azure' + subnet_id = 'azure' + virtual_network = 'azure' + size = 100 + return dict({ + 'resource_group': resource_group, + 'account_name': account_name, + 'pool_name': pool_name, + 'name': name, + 'location': location, + 'file_path': file_path, + 'subnet_name': subnet_id, + 'virtual_network': virtual_network, + 'size': size, + 'protocol_types': 'nfs', + 'tags': {'owner': 'laurentn'} + }) + + +def test_module_fail_when_required_args_missing(patch_ansible): # pylint: disable=unused-argument + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + volume_module() + print('Info: %s' % exc.value.args[0]['msg']) + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +def test_ensure_get_called_valid_volume(client_f): + set_module_args(set_default_args()) + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.netapp_client.volumes = MockAzureClient() + assert my_obj.get_azure_netapp_volume() is not None + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +def test_ensure_get_called_non_existing_volume(client_f): + data = dict(set_default_args()) + data['name'] = 'invalid' + set_module_args(data) + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.netapp_client.volumes = MockAzureClient() + assert my_obj.get_azure_netapp_volume() is None + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.create_azure_netapp_volume') +def test_ensure_create_called(mock_create, mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'create' + set_module_args(data) + mock_get.side_effect = [ + None, # first get + dict(mount_targets=[dict(ip_address='11.22.33.44')], # get after create + creation_token='abcd') + ] + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.netapp_client.volumes = MockAzureClient() + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + expected_mount_path = '11.22.33.44:/abcd' + assert exc.value.args[0]['mount_path'] == expected_mount_path + mock_create.assert_called_with() + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +def test_create(mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'create' + data['protocol_types'] = ['nfsv4.1'] + set_module_args(data) + mock_get.side_effect = [ + None, # first get + dict(mount_targets=[dict(ip_address='11.22.33.44')], # get after create + creation_token='abcd') + ] + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.azure_auth = Mock(subscription_id='1234') + my_obj._new_style = True + my_obj.netapp_client.volumes = MockAzureClient() + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + expected_mount_path = '11.22.33.44:/abcd' + assert exc.value.args[0]['mount_path'] == expected_mount_path + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +def test_create_exception(mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'create' + data['protocol_types'] = 'nfsv4.1' + set_module_args(data) + mock_get.side_effect = [ + None, # first get + dict(mount_targets=[dict(ip_address='11.22.33.44')], # get after create + creation_token='abcd') + ] + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.azure_auth = Mock(subscription_id='1234') + my_obj._new_style = True + my_obj.netapp_client.volumes = MockAzureClientRaise() + with pytest.raises(AnsibleFailJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + expected_msg = 'Error creating volume' + assert expected_msg in exc.value.args[0]['msg'] + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.create_azure_netapp_volume') +def test_ensure_create_called_but_fail_on_get(mock_create, mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'create' + set_module_args(data) + mock_get.side_effect = [ + None, # first get + dict(mount_targets=None, # get after create + creation_token='abcd') + ] + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.netapp_client.volumes = MockAzureClient() + with pytest.raises(AnsibleFailJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + error = 'Error: volume create was created successfully, but mount target(s) cannot be found - volume details:' + assert exc.value.args[0]['msg'].startswith(error) + mock_create.assert_called_with() + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.create_azure_netapp_volume') +def test_ensure_create_called_but_fail_on_mount_target(mock_create, mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'create' + set_module_args(data) + mock_get.return_value = None + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.netapp_client.volumes = MockAzureClient() + with pytest.raises(AnsibleFailJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + error = 'Error: volume create was created successfully, but cannot be found.' + assert exc.value.args[0]['msg'] == error + mock_create.assert_called_with() + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.delete_azure_netapp_volume') +def test_ensure_delete_called(mock_delete, mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['state'] = 'absent' + set_module_args(data) + client_f.return_value = Mock() + mock_get.return_value = Mock() + my_obj = volume_module() + my_obj.netapp_client.volumes = MockAzureClient() + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + mock_delete.assert_called_with() + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +def test_delete(mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'delete' + data['state'] = 'absent' + set_module_args(data) + mock_get.side_effect = [ + dict(mount_targets=[dict(ip_address='11.22.33.44')], # first get + creation_token='abcd') + ] + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.azure_auth = Mock(subscription_id='1234') + my_obj._new_style = True + my_obj.netapp_client.volumes = MockAzureClient() + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + expected_mount_path = '' + assert exc.value.args[0]['mount_path'] == expected_mount_path + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +def test_delete_exception(mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'delete' + data['state'] = 'absent' + set_module_args(data) + mock_get.side_effect = [ + dict(mount_targets=[dict(ip_address='11.22.33.44')], # first get + creation_token='abcd') + ] + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.azure_auth = Mock(subscription_id='1234') + my_obj._new_style = True + my_obj.netapp_client.volumes = MockAzureClientRaise() + with pytest.raises(AnsibleFailJson) as exc: + # add default args for exec_module + data['debug'] = False + my_obj.exec_module(**data) + expected_msg = 'Error deleting volume' + assert expected_msg in exc.value.args[0]['msg'] + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +def test_modify(mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'modify' + data['size'] = 200 + data['tags'] = {'added_tag': 'new_tag'} + set_module_args(data) + mock_get.side_effect = [ + dict(mount_targets=[dict(ip_address='11.22.33.44')], # first get + creation_token='abcd', + tags={}, + usage_threshold=0), + dict(mount_targets=[dict(ip_address='11.22.33.44')], # get after modify + creation_token='abcd', + usage_threshold=10000000) + ] + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.azure_auth = Mock(subscription_id='1234') + my_obj._new_style = True + my_obj.netapp_client.volumes = MockAzureClient() + with pytest.raises(AnsibleExitJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + assert exc.value.args[0]['changed'] + print('modify', exc.value.args[0]) + expected_mount_path = '11.22.33.44:/abcd' + assert exc.value.args[0]['mount_path'] == expected_mount_path + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +def test_modify_exception(mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'modify' + data['size'] = 200 + set_module_args(data) + mock_get.side_effect = [ + dict(mount_targets=[dict(ip_address='11.22.33.44')], # first get + creation_token='abcd', + usage_threshold=0), + dict(mount_targets=[dict(ip_address='11.22.33.44')], # get after modify + creation_token='abcd', + usage_threshold=10000000) + ] + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.azure_auth = Mock(subscription_id='1234') + my_obj._new_style = True + my_obj.netapp_client.volumes = MockAzureClientRaise() + with pytest.raises(AnsibleFailJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + expected_msg = 'Error modifying volume' + assert expected_msg in exc.value.args[0]['msg'] + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +@patch('ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume.AzureRMNetAppVolume.get_azure_netapp_volume') +def test_modify_not_supported(mock_get, client_f, patch_ansible): # pylint: disable=unused-argument + data = dict(set_default_args()) + data['name'] = 'modify' + data['location'] = 'east' + set_module_args(data) + mock_get.side_effect = [ + dict(mount_targets=[dict(ip_address='11.22.33.44')], # first get + creation_token='abcd', + usage_threshold=0, + location='west', + name='old_name'), + dict(mount_targets=[dict(ip_address='11.22.33.44')], # get after modify + creation_token='abcd', + usage_threshold=10000000) + ] + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.azure_auth = Mock(subscription_id='1234') + my_obj._new_style = True + my_obj.netapp_client.volumes = MockAzureClient() + with pytest.raises(AnsibleFailJson) as exc: + # add default args for exec_module + data['state'] = 'present' + data['debug'] = False + my_obj.exec_module(**data) + expected_msg = "Error: the following properties cannot be modified: {'location': 'east'}" + assert expected_msg in exc.value.args[0]['msg'] + + +@patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.netapp_client') +def test_get_export_policy_rules(client_f, patch_ansible): + set_module_args(set_default_args()) + client_f.return_value = Mock() + my_obj = volume_module() + my_obj.netapp_client.volumes = MockAzureClient() + rules = my_obj.get_export_policy_rules() + assert rules is None + del my_obj.parameters['protocol_types'] + rules = my_obj.get_export_policy_rules() + assert rules is None + my_obj.parameters['protocol_types'] = ['nFsv4.1'] + rules = my_obj.get_export_policy_rules() + assert rules is not None + rules = vars(rules) + assert 'rules' in rules + rules = rules['rules'] + assert rules + rule = vars(rules[0]) + assert rule['nfsv41'] + assert not rule['cifs'] + + +def test_dict_from_object(): + set_module_args(set_default_args()) + my_obj = volume_module() + # just for fun + module_dict = my_obj.dict_from_volume_object(my_obj) + print('Module dict', module_dict) + + rule_object = Mock() + rule_object.ip_address = '10.10.10.10' + export_policy_object = Mock() + export_policy_object.rules = [rule_object] + volume_object = Mock() + volume_object.export_policy = export_policy_object + volume_dict = my_obj.dict_from_volume_object(volume_object) + print('Volume dict', volume_dict) + assert 'export_policy' in volume_dict + assert 'rules' in volume_dict['export_policy'] + assert isinstance(volume_dict['export_policy']['rules'], list) + assert len(volume_dict['export_policy']['rules']) == 1 + assert 'ip_address' in volume_dict['export_policy']['rules'][0] diff --git a/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_volume_import.py b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_volume_import.py new file mode 100644 index 000000000..13d3bba29 --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/plugins/modules/test_azure_rm_netapp_volume_import.py @@ -0,0 +1,74 @@ +# (c) 2021, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: azure_rm_netapp_volume''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import sys + +import pytest +# from typing import Collection +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.azure.tests.unit.compat.mock import patch + + +if sys.version_info < (3, 5): + pytestmark = pytest.mark.skip('skipping as missing imports on 2.6 and 2.7') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +@pytest.fixture(name="patch_ansible") +def fixture_patch_ansible(): + with patch.multiple(basic.AnsibleModule, + fail_json=fail_json) as mocks: + yield mocks + + +# @patch('ansible_collections.netapp.azure.plugins.module_utils.azure_rm_netapp_common.AzureRMNetAppModuleBase.__init__') +def test_import_error(): + orig_import = __import__ + + def import_mock(name, *args): + print('importing: %s' % name) + if name.startswith('ansible_collections.netapp.azure.plugins.modules'): + # force a relead to go through secondary imports + sys.modules.pop(name, None) + if name in ('azure.core.exceptions', 'azure.mgmt.netapp.models'): + raise ImportError('forced error on %s' % name) + return orig_import(name, *args) + + # mock_base.return_value = Mock() + data = dict() + set_module_args(data) + with patch('builtins.__import__', side_effect=import_mock): + from ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume import IMPORT_ERRORS + assert any('azure.core.exceptions' in error for error in IMPORT_ERRORS) + assert any('azure.mgmt.netapp.models' in error for error in IMPORT_ERRORS) + + +def test_main(patch_ansible): # pylint: disable=unused-argument + data = dict() + set_module_args(data) + from ansible_collections.netapp.azure.plugins.modules.azure_rm_netapp_volume import main + with pytest.raises(AnsibleFailJson) as exc: + main() + expected_msg = "missing required arguments:" + assert expected_msg in exc.value.args[0]['msg'] diff --git a/ansible_collections/netapp/azure/tests/unit/requirements.txt b/ansible_collections/netapp/azure/tests/unit/requirements.txt new file mode 100644 index 000000000..0b89f6365 --- /dev/null +++ b/ansible_collections/netapp/azure/tests/unit/requirements.txt @@ -0,0 +1,3 @@ +azure-mgmt-netapp ; python_version >= '2.7' +msrestazure ; python_version >= '3.5' +requests ; python_version >= '2.7' |