summaryrefslogtreecommitdiffstats
path: root/ansible_collections/purestorage/fusion/tests/unit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/purestorage/fusion/tests/unit
parentInitial commit. (diff)
downloadansible-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')
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/README.md15
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/mocks/__init__.py0
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/mocks/module_mock.py38
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/mocks/operation_mock.py24
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/module_utils/__init__.py0
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/module_utils/test_networking.py58
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/module_utils/test_operations.py230
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/module_utils/test_parsing.py138
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/module_utils/test_prerequisites.py116
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/modules/__init__.py0
-rw-r--r--ansible_collections/purestorage/fusion/tests/unit/modules/test_fusion_az.py446
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)