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/purestorage/fusion/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/purestorage/fusion/tests/unit')
11 files changed, 1065 insertions, 0 deletions
diff --git a/ansible_collections/purestorage/fusion/tests/unit/README.md b/ansible_collections/purestorage/fusion/tests/unit/README.md new file mode 100644 index 000000000..248a608ba --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/README.md @@ -0,0 +1,15 @@ +# Unit tests + +Unit tests aims at testing specific functions of modules. + +Each module as a whole should be tested in Functional tests. + +## Running tests + +```bash +pytest tests/unit +``` + +## Adding new tests + +See already existing tests for inspiration. diff --git a/ansible_collections/purestorage/fusion/tests/unit/mocks/__init__.py b/ansible_collections/purestorage/fusion/tests/unit/mocks/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/mocks/__init__.py diff --git a/ansible_collections/purestorage/fusion/tests/unit/mocks/module_mock.py b/ansible_collections/purestorage/fusion/tests/unit/mocks/module_mock.py new file mode 100644 index 000000000..cb6d489e1 --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/mocks/module_mock.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +# (c) 2023, Denys Denysyev (ddenysyev@purestorage.com) +# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from unittest.mock import MagicMock + + +class ModuleSucceeded(Exception): + pass + + +class ModuleFailed(Exception): + pass + + +class ModuleMock(MagicMock): + def __init__(self, params, check_mode=False): + super().__init__() + + self.params = params + self.check_mode = check_mode + + # mocking exit_json function, so we can check if it was successfully called + self.exit_json = MagicMock() + + def fail_json(self, **kwargs): + raise ModuleFailed(str(kwargs)) + + def fail_on_missing_params(self, required_params=None): + if required_params is not None: + for param in required_params: + if param not in self.params: + raise ModuleFailed(f"Parameter '{param}' is missing") diff --git a/ansible_collections/purestorage/fusion/tests/unit/mocks/operation_mock.py b/ansible_collections/purestorage/fusion/tests/unit/mocks/operation_mock.py new file mode 100644 index 000000000..99487ddfa --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/mocks/operation_mock.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# (c) 2023, Denys Denysyev (ddenysyev@purestorage.com) +# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from enum import Enum + + +class OperationStatus(str, Enum): + PENDING = "Pending" + ABORTING = "Aborting" + FAILED = "Failed" + SUCCEDED = "Succeeded" + + +class OperationMock: + def __init__(self, id, status, retry_in=1): + self.id = id + self.status = status + self.retry_in = retry_in diff --git a/ansible_collections/purestorage/fusion/tests/unit/module_utils/__init__.py b/ansible_collections/purestorage/fusion/tests/unit/module_utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/module_utils/__init__.py diff --git a/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_networking.py b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_networking.py new file mode 100644 index 000000000..13437456a --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_networking.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# (c) 2023, Jan Kodera (jkodera@purestorage.com) +# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.purestorage.fusion.plugins.module_utils.networking import ( + is_valid_address, + is_valid_network, + is_address_in_network, +) + + +def test_valid_address(): + assert is_valid_address("0.0.0.0") + assert is_valid_address("1.1.1.1") + assert is_valid_address("192.168.1.2") + assert is_valid_address("255.255.255.255") + + +def test_invalid_address(): + assert not is_valid_address("256.1.1.1") + assert not is_valid_address("1.256.1.1") + assert not is_valid_address("1.1.256.1") + assert not is_valid_address("1.1.1.256") + assert not is_valid_address("1.1.1.256") + assert not is_valid_address("010.010.010.010") + assert not is_valid_address("1.1.1") + assert not is_valid_address("hostname") + assert not is_valid_address("0x1.0x2.0x3.0x4") + + +def test_valid_network(): + assert is_valid_network("0.0.0.0/8") + assert is_valid_network("1.1.1.1/12") + assert is_valid_network("192.168.1.2/24") + assert is_valid_network("255.255.255.255/32") + + +def test_invalid_network(): + assert not is_valid_network("1.1.1.1") + assert not is_valid_network("1.1.1.1/") + assert not is_valid_network("1.1.1.1/1") + assert not is_valid_network("1.1.1.1/7") + assert not is_valid_network("1.1.1.1/33") + + +def test_address_is_in_network(): + assert is_address_in_network("1.1.1.1", "1.1.0.0/16") + assert is_address_in_network("1.1.1.1", "1.1.1.1/32") + + +def test_address_is_not_in_network(): + assert not is_address_in_network("1.1.1.1", "1.2.0.0/16") + assert not is_address_in_network("1.1.1.1", "1.1.1.2/32") diff --git a/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_operations.py b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_operations.py new file mode 100644 index 000000000..6b42eb35f --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_operations.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- + +# (c) 2023, Denys Denysyev (ddenysyev@purestorage.com) +# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +import time + +from ansible_collections.purestorage.fusion.plugins.module_utils.errors import ( + OperationException, +) +from ansible_collections.purestorage.fusion.tests.helpers import ( + ApiExceptionsMockGenerator, +) +from ansible_collections.purestorage.fusion.tests.unit.mocks.operation_mock import ( + OperationMock, + OperationStatus, +) + +__metaclass__ = type + +import fusion as purefusion +from urllib3.exceptions import HTTPError + +from unittest.mock import Mock, MagicMock, call, patch +import pytest +from ansible_collections.purestorage.fusion.plugins.module_utils import operations + +time.sleep = MagicMock() # mock time.sleep function globally +current_module = ( + "ansible_collections.purestorage.fusion.tests.unit.module_utils.test_operations" +) + + +class TestAwaitOperations: + @patch(f"{current_module}.operations.purefusion.OperationsApi.__new__") + def test_await_success_op(self, mock_op_api): + """ + Should return operation + """ + # Mock operation + op = OperationMock("1", OperationStatus.SUCCEDED) + + # Mock operations api + mock_op_api_obj = MagicMock() + mock_op_api.return_value = mock_op_api_obj + mock_op_api_obj.get_operation = Mock(return_value=op) + + # Mock fusion + fusion_mock = MagicMock() + + # Test function + op1 = operations.await_operation(fusion_mock, op) + + # Assertions + assert op == op1 + mock_op_api_obj.get_operation.assert_called_once_with(op.id) + + @patch(f"{current_module}.operations.purefusion.OperationsApi.__new__") + def test_await_failed_op(self, mock_op_api): + """ + Should raise OperationException + """ + # Mock operation + op = OperationMock("1", OperationStatus.FAILED) + + # Mock exception + op_exception = OperationException(op, None) + + # Mock operations api + mock_op_api_obj = MagicMock() + mock_op_api.return_value = mock_op_api_obj + mock_op_api_obj.get_operation = Mock(return_value=op) + + # Mock fusion + fusion_mock = MagicMock() + + # Test function + with pytest.raises(Exception) as exception: + operations.await_operation(fusion_mock, op) + + # Assertions + assert ( + type(exception) is type(op_exception) + and exception.args == op_exception.args + ) + mock_op_api_obj.get_operation.assert_called_once_with(op.id) + + @patch(f"{current_module}.operations.purefusion.OperationsApi.__new__") + def test_await_pending_op(self, mock_op_api): + """ + Should return operation + """ + # Mock operation + op1 = OperationMock("1", OperationStatus.PENDING) + op2 = OperationMock("1", OperationStatus.SUCCEDED) + + # Mock operations api + mock_op_api_obj = MagicMock() + mock_op_api.return_value = mock_op_api_obj + mock_op_api_obj.get_operation = Mock(side_effect=[op1, op2]) + + # Mock fusion + fusion_mock = MagicMock() + + # Test function + op = operations.await_operation(fusion_mock, op1) + + # Assertions + assert op == op2 + calls = [call(op1.id), call(op1.id)] + mock_op_api_obj.get_operation.assert_has_calls(calls) + + @patch(f"{current_module}.operations.purefusion.OperationsApi.__new__") + def test_await_failed_pending_op(self, mock_op_api): + """ + Should raise OperationException + """ + # Mock operation + op1 = OperationMock("1", OperationStatus.PENDING) + op2 = OperationMock("1", OperationStatus.FAILED) + + # Mock exception + op_exception = OperationException(op2, None) + + # Mock operations api + mock_op_api_obj = MagicMock() + mock_op_api.return_value = mock_op_api_obj + mock_op_api_obj.get_operation = Mock(side_effect=[op1, op2]) + + # Mock fusion + fusion_mock = MagicMock() + + # Test function + with pytest.raises(Exception) as exception: + operations.await_operation(fusion_mock, op1) + + # Assertions + assert ( + type(exception) is type(op_exception) + and exception.args == op_exception.args + ) + calls = [call(op1.id), call(op1.id)] + mock_op_api_obj.get_operation.assert_has_calls(calls) + + @patch(f"{current_module}.operations.purefusion.OperationsApi.__new__") + def test_await_api_exception(self, mock_op_api): + """ + Should raise ApiException + """ + # Mock exceptions + api_exception = ApiExceptionsMockGenerator.create_conflict() + + # Mock operation + op = OperationMock("1", OperationStatus.FAILED) + + # Mock operations api + mock_op_api_obj = MagicMock() + mock_op_api.return_value = mock_op_api_obj + mock_op_api_obj.get_operation = Mock(side_effect=api_exception) + + # Mock fusion + fusion_mock = MagicMock() + + # Test function + with pytest.raises(purefusion.rest.ApiException) as exception: + operations.await_operation(fusion_mock, op) + + # Assertions + assert ( + type(exception) is type(api_exception) + and exception.args == api_exception.args + ) + mock_op_api_obj.get_operation.assert_called_once_with(op) + + @patch(f"{current_module}.operations.purefusion.OperationsApi.__new__") + def test_await_http_exception(self, mock_op_api): + """ + Should raise OperationException + """ + # Mock operation + op = OperationMock("1", OperationStatus.FAILED) + + # Mock exceptions + http_error = HTTPError() + op_exception = OperationException(op, http_error) + # Mock operations api + mock_op_api_obj = MagicMock() + mock_op_api.return_value = mock_op_api_obj + mock_op_api_obj.get_operation = Mock(side_effect=http_error) + + # Mock fusion + fusion_mock = MagicMock() + + # Test function + with pytest.raises(OperationException) as exception: + operations.await_operation(fusion_mock, op) + + # Assertions + assert ( + type(exception) is type(op_exception) + and exception.args == op_exception.args + ) + mock_op_api_obj.get_operation.assert_called_once_with(op) + + @patch(f"{current_module}.operations.purefusion.OperationsApi.__new__") + def test_await_failed_op_without_failing(self, mock_op_api): + """ + Should return failed operation + """ + # Mock operation + op = OperationMock("1", OperationStatus.FAILED) + + # Mock operations api + mock_op_api_obj = MagicMock() + mock_op_api.return_value = mock_op_api_obj + mock_op_api_obj.get_operation = Mock(return_value=op) + + # Mock fusion + fusion_mock = MagicMock() + + # Test function + op_res = operations.await_operation( + fusion_mock, op, fail_playbook_if_operation_fails=False + ) + + # Assertions + assert op_res == op + mock_op_api_obj.get_operation.assert_called_once_with(op.id) diff --git a/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_parsing.py b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_parsing.py new file mode 100644 index 000000000..7e2a1cc78 --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_parsing.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +# (c) 2023, Jan Kodera (jkodera@purestorage.com) +# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import ( + parse_number_with_metric_suffix, + parse_minutes, +) + +import pytest + + +class MockException(Exception): + pass + + +class MockModule: + def fail_json(self, msg): + raise MockException() + + +def test_parsing_valid_number(): + module = MockModule() + assert parse_number_with_metric_suffix(module, "0") == 0 + assert parse_number_with_metric_suffix(module, "1") == 1 + assert parse_number_with_metric_suffix(module, "1K") == 1024 + assert parse_number_with_metric_suffix(module, "1 K") == 1024 + assert parse_number_with_metric_suffix(module, "124 M") == 124 * 1024 * 1024 + assert parse_number_with_metric_suffix(module, "10 G") == 10 * 1024 * 1024 * 1024 + assert ( + parse_number_with_metric_suffix(module, "20 T") + == 20 * 1024 * 1024 * 1024 * 1024 + ) + assert ( + parse_number_with_metric_suffix(module, "30 P") + == 30 * 1024 * 1024 * 1024 * 1024 * 1024 + ) + assert ( + parse_number_with_metric_suffix(module, "30000 P") + == 30000 * 1024 * 1024 * 1024 * 1024 * 1024 + ) + assert parse_number_with_metric_suffix(module, "0", factor=1000) == 0 + assert parse_number_with_metric_suffix(module, "1", factor=1000) == 1 + assert parse_number_with_metric_suffix(module, "1K", factor=1000) == 1000 + assert ( + parse_number_with_metric_suffix(module, "124M", factor=1000) + == 124 * 1000 * 1000 + ) + assert parse_number_with_metric_suffix(module, "1.5K", factor=1000) == 1500 + assert parse_number_with_metric_suffix(module, "1.5K", factor=1024) == 1536 + + +def test_parsing_invalid_number(): + module = MockModule() + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "") + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "102X") + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "102 N") + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "102 N", factor=1000) + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "million") + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "K") + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "K1") + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "1K1") + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "1 K1") + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "M") + with pytest.raises(MockException): + assert parse_number_with_metric_suffix(module, "hello world") + + +def test_parsing_valid_time_period(): + module = MockModule() + assert parse_minutes(module, "10") == 10 + assert parse_minutes(module, "2h") == 120 + assert parse_minutes(module, "2H") == 120 + assert parse_minutes(module, "14D") == 14 * 24 * 60 + assert parse_minutes(module, "1W") == 7 * 24 * 60 + assert parse_minutes(module, "12Y") == 12 * 365 * 24 * 60 + assert ( + parse_minutes(module, "10Y20W30D40H50M") + == 10 * 365 * 24 * 60 + 20 * 7 * 24 * 60 + 30 * 24 * 60 + 40 * 60 + 50 + ) + assert ( + parse_minutes(module, "10Y20W30D40H") + == 10 * 365 * 24 * 60 + 20 * 7 * 24 * 60 + 30 * 24 * 60 + 40 * 60 + ) + assert ( + parse_minutes(module, "10Y20W30D") + == 10 * 365 * 24 * 60 + 20 * 7 * 24 * 60 + 30 * 24 * 60 + ) + assert parse_minutes(module, "10Y20W") == 10 * 365 * 24 * 60 + 20 * 7 * 24 * 60 + assert ( + parse_minutes(module, "20W30D40H50M") + == 20 * 7 * 24 * 60 + 30 * 24 * 60 + 40 * 60 + 50 + ) + assert parse_minutes(module, "30D40H50M") == 30 * 24 * 60 + 40 * 60 + 50 + assert parse_minutes(module, "40H50M") == 40 * 60 + 50 + assert parse_minutes(module, "30D50M") == 30 * 24 * 60 + 50 + assert parse_minutes(module, "20W40H") == 20 * 7 * 24 * 60 + 40 * 60 + + +def test_parsing_invalid_time_period(): + module = MockModule() + with pytest.raises(MockException): + assert parse_minutes(module, "") + with pytest.raises(MockException): + assert parse_minutes(module, "1s") + with pytest.raises(MockException): + assert parse_minutes(module, "1S") + with pytest.raises(MockException): + assert parse_minutes(module, "1V") + with pytest.raises(MockException): + assert parse_minutes(module, "0M") + with pytest.raises(MockException): + assert parse_minutes(module, "0H10M") + with pytest.raises(MockException): + assert parse_minutes(module, "0H10M") + with pytest.raises(MockException): + assert parse_minutes(module, "0D10H10M") + with pytest.raises(MockException): + assert parse_minutes(module, "01W10D10H10M") + with pytest.raises(MockException): + assert parse_minutes(module, "01Y0H10M") + with pytest.raises(MockException): + assert parse_minutes(module, "1V") diff --git a/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_prerequisites.py b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_prerequisites.py new file mode 100644 index 000000000..0158878cf --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/module_utils/test_prerequisites.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# (c) 2023, Jan Kodera (jkodera@purestorage.com) +# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.purestorage.fusion.plugins.module_utils.prerequisites import ( + _parse_version, + _parse_version_requirements, + _version_satisfied, +) + +import pytest + + +def test_version(): + # VALID + assert _parse_version("1.0") == (1, 0, None) + assert _parse_version("1.0.0") == (1, 0, 0) + assert _parse_version("2.3.4") == (2, 3, 4) + assert _parse_version("2.3.5a") == (2, 3, 5) + assert _parse_version("2.3.6-release") == (2, 3, 6) + # INVALID + assert _parse_version("1") is None + assert _parse_version("1.a") is None + assert _parse_version("1.1a") is None + assert _parse_version("a.1") is None + assert _parse_version("1.") is None + assert _parse_version("1..") is None + assert _parse_version("1.0.1.0") is None + assert _parse_version("1.0.1.a") is None + + +def test_requirements(): + # VALID + assert _parse_version_requirements(">= 1.0") == [(">=", (1, 0, None))] + assert _parse_version_requirements(">=1.0.1") == [(">=", (1, 0, 1))] + assert _parse_version_requirements(">= 2.0.2-release") == [(">=", (2, 0, 2))] + assert _parse_version_requirements(" >=3.0.3b") == [(">=", (3, 0, 3))] + assert _parse_version_requirements("<= 3.3.3") == [("<=", (3, 3, 3))] + assert _parse_version_requirements("= 3.0.3") == [("=", (3, 0, 3))] + assert _parse_version_requirements("== 5.3.1") == [("==", (5, 3, 1))] + assert _parse_version_requirements("< 4.1.2") == [("<", (4, 1, 2))] + assert _parse_version_requirements("> 1.3.4") == [(">", (1, 3, 4))] + assert _parse_version_requirements("> 1.3.4, < 2.0") == [ + (">", (1, 3, 4)), + ("<", (2, 0, None)), + ] + assert _parse_version_requirements(">1.3.4 , <2.0") == [ + (">", (1, 3, 4)), + ("<", (2, 0, None)), + ] + assert _parse_version_requirements("> 1.3.4 ,< 2.0") == [ + (">", (1, 3, 4)), + ("<", (2, 0, None)), + ] + assert _parse_version_requirements(">1.3.4,<2.0") == [ + (">", (1, 3, 4)), + ("<", (2, 0, None)), + ] + assert _parse_version_requirements(">1.3.4,<2.0, != 3.4.1") == [ + (">", (1, 3, 4)), + ("<", (2, 0, None)), + ("!=", (3, 4, 1)), + ] + # INVALID + with pytest.raises(ValueError): + _parse_version_requirements(">>1.3.4") + with pytest.raises(ValueError): + _parse_version_requirements("<<1.3.4") + with pytest.raises(ValueError): + _parse_version_requirements("=>1.3.4,,3.0") + with pytest.raises(ValueError): + _parse_version_requirements("=<1.3.4,") + with pytest.raises(ValueError): + _parse_version_requirements("=<1.3.4") + + +def test_version_satisfied(): + assert _version_satisfied("1.0", ">=1.0, <2.0") is True + assert _version_satisfied("1.0.1", ">=1.0, <2.0") is True + assert _version_satisfied("2.0", ">=1.0, <2.0") is False + assert _version_satisfied("2.0.0", ">=1.0, <2.0") is False + assert _version_satisfied("2.0.1", ">=1.0, <2.0") is False + assert _version_satisfied("1.0.0", ">=1.0.0") is True + assert _version_satisfied("1.0", ">=1.0.0") is True + assert _version_satisfied("1.0", ">=1.0") is True + assert _version_satisfied("1.0.1", ">=1.0") is True + assert _version_satisfied("1.0.1", ">=1.0.0") is True + assert _version_satisfied("1.0.1", "<=1.0.0") is False + assert _version_satisfied("1.0.0", "<=1.0.0") is True + assert _version_satisfied("1.0", "<=1.0.0") is True + assert _version_satisfied("1.0", "<=1.0.1") is True + assert _version_satisfied("1.0", "<=1.0") is True + assert _version_satisfied("1.0", "<1.0") is False + assert _version_satisfied("1.0.0", "<1.0") is False + assert _version_satisfied("1.0.0", "<1.1") is True + assert _version_satisfied("1.0.0", "<1.0.1") is True + assert _version_satisfied("1.0", ">1.0") is False + assert _version_satisfied("1.0.1", ">1.0") is False + assert _version_satisfied("1.0", ">1.0.0") is False + assert _version_satisfied("1.0.0", ">1.0.0") is False + assert _version_satisfied("1.0.1", ">1.0.0") is True + assert _version_satisfied("1.0", "==1.0") is True + assert _version_satisfied("1.0", "=1.0") is True + assert _version_satisfied("1.0.0", "==1.0") is True + assert _version_satisfied("1.0.1", "==1.0") is True + assert _version_satisfied("1.0", "==1.0.0") is True + assert _version_satisfied("1.0", "==1.0.1") is False + assert _version_satisfied("1.0", "!=1.0.1") is True + assert _version_satisfied("1.0", "!=1.0.0") is False + assert _version_satisfied("1.0.1", "!=1.0") is False + assert _version_satisfied("1.0", "!=1.0") is False diff --git a/ansible_collections/purestorage/fusion/tests/unit/modules/__init__.py b/ansible_collections/purestorage/fusion/tests/unit/modules/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/modules/__init__.py diff --git a/ansible_collections/purestorage/fusion/tests/unit/modules/test_fusion_az.py b/ansible_collections/purestorage/fusion/tests/unit/modules/test_fusion_az.py new file mode 100644 index 000000000..a384506d8 --- /dev/null +++ b/ansible_collections/purestorage/fusion/tests/unit/modules/test_fusion_az.py @@ -0,0 +1,446 @@ +# -*- coding: utf-8 -*- + +# (c) 2023, Denys Denysyev (ddenysyev@purestorage.com) +# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from unittest.mock import MagicMock, Mock, patch + +import fusion as purefusion +import pytest +from ansible_collections.purestorage.fusion.plugins.module_utils.errors import ( + OperationException, +) +from ansible_collections.purestorage.fusion.plugins.modules import fusion_az +from ansible_collections.purestorage.fusion.tests.helpers import ( + ApiExceptionsMockGenerator, +) +from ansible_collections.purestorage.fusion.tests.unit.mocks.module_mock import ( + ModuleMock, +) +from ansible_collections.purestorage.fusion.tests.unit.mocks.operation_mock import ( + OperationMock, + OperationStatus, +) + +fusion_az.setup_fusion = MagicMock() +current_module = ( + "ansible_collections.purestorage.fusion.tests.unit.modules.test_fusion_az" +) + + +def default_module_az_params(state="present", display_name="foo_az"): + module_params = { + "state": state, + "name": "foo", + "region": "region1", + "display_name": display_name, + "issuer_id": "ABCD12345", + "private_key_file": "az-admin-private-key.pem", + } + return module_params + + +class TestCreateAZ: + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_create_az_without_disp_name(self, await_operation_mock, mock_az_api): + """ + Should create az successfully + """ + # Mock operation + op = OperationMock("1", OperationStatus.SUCCEDED) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.create_availability_zone = MagicMock(return_value=op) + + # Mock await operation + await_operation_mock.return_value = op + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("present", None) + moduleMock = ModuleMock(module_params) + + # Test function + fusion_az.create_az(moduleMock, fusion_mock) + + # Assertions + azone = purefusion.AvailabilityZonePost( + name=module_params["name"], + display_name=module_params["name"], + ) + mock_az_api_obj.create_availability_zone.assert_called_with( + azone, region_name=module_params["region"] + ) + await_operation_mock.assert_called_once_with(fusion_mock, op) + moduleMock.exit_json.assert_called_once_with(changed=True) + + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_create_az_check_mode(self, await_operation_mock, mock_az_api): + """ + Should only exit_json + """ + # Mock operation + op = OperationMock("1", OperationStatus.SUCCEDED) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.create_availability_zone = MagicMock(return_value=op) + + # Mock await operation + await_operation_mock.return_value = op + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("present") + moduleMock = ModuleMock(module_params, check_mode=True) + + # Test function + fusion_az.create_az(moduleMock, fusion_mock) + + # Assertions + mock_az_api_obj.create_availability_zone.assert_not_called() + await_operation_mock.assert_not_called() + moduleMock.exit_json.assert_called_once_with(changed=True) + + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_create_az_with_disp_name(self, await_operation_mock, mock_az_api): + """ + Should create az successfully + """ + # Mock operation + op = OperationMock("1", OperationStatus.SUCCEDED) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.create_availability_zone = MagicMock(return_value=op) + + # Mock await operation + await_operation_mock.return_value = op + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("present") + moduleMock = ModuleMock(module_params) + + # Test function + fusion_az.create_az(moduleMock, fusion_mock) + + # Assertions + azone = purefusion.AvailabilityZonePost( + name=module_params["name"], + display_name=module_params["display_name"], + ) + mock_az_api_obj.create_availability_zone.assert_called_with( + azone, region_name=module_params["region"] + ) + await_operation_mock.assert_called_once_with(fusion_mock, op) + moduleMock.exit_json.assert_called_once_with(changed=True) + + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_create_az_conflict(self, await_operation_mock, mock_az_api): + """ + Should raise api exception + """ + # Mock exceptions + api_exception = ApiExceptionsMockGenerator.create_conflict() + + # Mock operation + op = OperationMock("1", OperationStatus.FAILED) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.create_availability_zone = Mock(side_effect=api_exception) + + # Mock await operation + await_operation_mock.return_value = op + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("present") + moduleMock = ModuleMock(module_params) + + # Test function + with pytest.raises(purefusion.rest.ApiException) as exception: + fusion_az.create_az(moduleMock, fusion_mock) + azone = purefusion.AvailabilityZonePost( + name=module_params["name"], + display_name=module_params["display_name"], + ) + + # Assertions + assert ( + type(exception) is type(api_exception) + and exception.args == api_exception.args + ) + mock_az_api_obj.create_availability_zone.assert_called_with( + azone, region_name=module_params["region"] + ) + await_operation_mock.assert_not_called() + moduleMock.exit_json.assert_not_called() + + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_create_az_not_found(self, await_operation_mock, mock_az_api): + """ + Should raise api exception + """ + # Mock exceptions + api_exception = ApiExceptionsMockGenerator.create_not_found() + + # Mock operation + op = OperationMock("1", OperationStatus.FAILED) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.create_availability_zone = Mock(side_effect=api_exception) + + # Mock await operation + await_operation_mock.return_value = op + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("present") + moduleMock = ModuleMock(module_params) + + # Test function + with pytest.raises(purefusion.rest.ApiException) as exception: + fusion_az.create_az(moduleMock, fusion_mock) + azone = purefusion.AvailabilityZonePost( + name=module_params["name"], + display_name=module_params["display_name"], + ) + + # Assertions + assert ( + type(exception) is type(api_exception) + and exception.args == api_exception.args + ) + mock_az_api_obj.create_availability_zone.assert_called_with( + azone, region_name=module_params["region"] + ) + await_operation_mock.assert_not_called() + moduleMock.exit_json.assert_not_called() + + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_create_az_op_fails(self, await_operation_mock, mock_az_api): + """ + Should raise operation exception + """ + # Mock operation + op = OperationMock("1", OperationStatus.FAILED) + + # Mock exception + op_exception = OperationException(op, None) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.create_availability_zone = MagicMock(return_value=op) + + # Mock await operation + await_operation_mock.side_effect = op_exception + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("present") + moduleMock = ModuleMock(module_params) + + # Test function + with pytest.raises(Exception) as exception: + fusion_az.create_az(moduleMock, fusion_mock) + azone = purefusion.AvailabilityZonePost( + name=module_params["name"], + display_name=module_params["display_name"], + ) + + # Assertions + assert ( + type(exception) is type(op_exception) + and exception.args == op_exception.args + ) + mock_az_api_obj.create_availability_zone.assert_called_with( + azone, region_name=module_params["region"] + ) + await_operation_mock.assert_called_once(fusion_mock, op) + moduleMock.exit_json.assert_not_called() + + +class TestDeleteAZ: + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_delete_az_successfully(self, await_operation_mock, mock_az_api): + """ + Should delete az successfully + """ + # Mock operation + op = OperationMock("1", OperationStatus.SUCCEDED) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.delete_availability_zone = MagicMock(return_value=op) + + # Mock await operation + await_operation_mock.return_value = op + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("absent") + moduleMock = ModuleMock(module_params) + + # Test function + fusion_az.delete_az(moduleMock, fusion_mock) + + # Assertions + mock_az_api_obj.delete_availability_zone.assert_called_with( + availability_zone_name=module_params["name"], + region_name=module_params["region"], + ) + await_operation_mock.assert_called_once_with(fusion_mock, op) + moduleMock.exit_json.assert_called_once_with(changed=True) + + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_create_az_conflict(self, await_operation_mock, mock_az_api): + """ + Should raise api exception + """ + # Mock exceptions + api_exception = ApiExceptionsMockGenerator.create_conflict() + + # Mock operation + op = OperationMock("1", OperationStatus.FAILED) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.delete_availability_zone = Mock(side_effect=api_exception) + + # Mock await operation + await_operation_mock.return_value = op + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("absent") + moduleMock = ModuleMock(module_params) + + # Test function + with pytest.raises(purefusion.rest.ApiException) as exception: + fusion_az.delete_az(moduleMock, fusion_mock) + + # Assertions + assert ( + type(exception) is type(api_exception) + and exception.args == api_exception.args + ) + mock_az_api_obj.delete_availability_zone.assert_called_with( + region_name=module_params["region"], + availability_zone_name=module_params["name"], + ) + await_operation_mock.assert_not_called() + moduleMock.exit_json.assert_not_called() + + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_create_az_op_fails(self, await_operation_mock, mock_az_api): + """ + Should raise operation exception + """ + # Mock operation + op = OperationMock("1", OperationStatus.FAILED) + + # Mock exception + op_exception = OperationException(op, None) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.delete_availability_zone = MagicMock(return_value=op) + + # Mock await operation + await_operation_mock.side_effect = op_exception + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("absent") + moduleMock = ModuleMock(module_params) + + # Test function + with pytest.raises(OperationException) as exception: + fusion_az.delete_az(moduleMock, fusion_mock) + # Assertions + assert ( + type(exception) is type(op_exception) + and exception.args == op_exception.args + ) + mock_az_api_obj.delete_availability_zone.assert_called_with( + region_name=module_params["region"], + availability_zone_name=module_params["name"], + ) + await_operation_mock.assert_called_once(fusion_mock, op) + moduleMock.exit_json.assert_not_called() + + @patch(f"{current_module}.fusion_az.purefusion.AvailabilityZonesApi.__new__") + @patch(f"{current_module}.fusion_az.await_operation") + def test_delete_az_check_mode(self, await_operation_mock, mock_az_api): + """ + Should only exit_json + """ + # Mock operation + op = OperationMock("1", OperationStatus.SUCCEDED) + + # Mock az api + mock_az_api_obj = MagicMock() + mock_az_api.return_value = mock_az_api_obj + mock_az_api_obj.delete_availability_zone = MagicMock(return_value=op) + + # Mock await operation + await_operation_mock.return_value = op + + # Mock fusion + fusion_mock = MagicMock() + + # Mock Module + module_params = default_module_az_params("absent") + moduleMock = ModuleMock(module_params, check_mode=True) + + # Test function + fusion_az.delete_az(moduleMock, fusion_mock) + + # Assertions + mock_az_api_obj.delete_availability_zone.assert_not_called() + await_operation_mock.assert_not_called() + moduleMock.exit_json.assert_called_once_with(changed=True) |