From e781f48f1521ef3c269b9a45e14a8cdf9ffa621f Mon Sep 17 00:00:00 2001 From: Jonas L Date: Mon, 11 Mar 2024 18:03:26 +0100 Subject: [PATCH] chore: add `fail_on_invalid_params` helper (#470) ##### SUMMARY Add a small helper to validate parameters while executing the module. --------- Co-authored-by: Justin Jeffery --- plugins/module_utils/hcloud.py | 27 ++++++++++++++++++++ tests/unit/conftest.py | 15 ++++++++++++ tests/unit/module_utils/test_hcloud.py | 34 ++++++++++++++++++++------ 3 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 tests/unit/conftest.py diff --git a/plugins/module_utils/hcloud.py b/plugins/module_utils/hcloud.py index eab0aef..6039130 100644 --- a/plugins/module_utils/hcloud.py +++ b/plugins/module_utils/hcloud.py @@ -10,6 +10,10 @@ from typing import Any, NoReturn from ansible.module_utils.basic import AnsibleModule as AnsibleModuleBase, env_fallback from ansible.module_utils.common.text.converters import to_native +from ansible.module_utils.common.validation import ( + check_missing_parameters, + check_required_one_of, +) from .client import ClientException, client_check_required_lib, client_get_by_name_or_id from .vendor.hcloud import APIException, Client, HCloudException @@ -94,6 +98,29 @@ class AnsibleHCloud: def _mark_as_changed(self) -> None: self.result["changed"] = True + def fail_on_invalid_params( + self, + *, + required: list[str] | None = None, + required_one_of: list[list[str]] | None = None, + ) -> None: + """ + Run additional validation that cannot be done in the argument spec validation. + + :param required_params: Check that terms exists in the module params. + :param required_one_of: Check each list of terms to ensure at least one exists in the module parameters. + """ + try: + if required: + check_missing_parameters(self.module.params, required) + + if required_one_of: + params_without_nones = {k: v for k, v in self.module.params.items() if v is not None} + check_required_one_of(required_one_of, params_without_nones) + + except TypeError as e: + self.module.fail_json(msg=to_native(e)) + @classmethod def base_module_arguments(cls): return { diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 0000000..af891af --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from unittest.mock import MagicMock + +import pytest + + +@pytest.fixture() +def module(): + obj = MagicMock() + obj.params = { + "api_token": "dummy", + "api_endpoint": "https://api.hetzner.cloud/v1", + } + return obj diff --git a/tests/unit/module_utils/test_hcloud.py b/tests/unit/module_utils/test_hcloud.py index c1a9ffb..2f5e750 100644 --- a/tests/unit/module_utils/test_hcloud.py +++ b/tests/unit/module_utils/test_hcloud.py @@ -2,8 +2,8 @@ from __future__ import annotations import traceback from datetime import datetime, timezone -from unittest.mock import MagicMock +import pytest from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import AnsibleHCloud from ansible_collections.hetzner.hcloud.plugins.module_utils.vendor.hcloud import ( APIException, @@ -16,12 +16,7 @@ from ansible_collections.hetzner.hcloud.plugins.module_utils.vendor.hcloud.actio ) -def test_hcloud_fail_json_hcloud(): - module = MagicMock() - module.params = { - "api_token": "fake_token", - "api_endpoint": "https://api.hetzner.cloud/v1", - } +def test_hcloud_fail_json_hcloud(module): AnsibleHCloud.represent = "hcloud_test" hcloud = AnsibleHCloud(module) @@ -123,3 +118,28 @@ def test_hcloud_fail_json_hcloud(): } }, ) + + +@pytest.mark.parametrize( + ("kwargs", "msg"), + [ + ({"required": ["key1"]}, None), + ({"required": ["missing"]}, "missing required arguments: missing"), + ({"required_one_of": [["key1", "missing"]]}, None), + ({"required_one_of": [["missing1", "missing2"]]}, "one of the following is required: missing1, missing2"), + ], +) +def test_hcloud_fail_on_invalid_params(module, kwargs, msg): + AnsibleHCloud.represent = "hcloud_test" + hcloud = AnsibleHCloud(module) + + module.params = { + "key1": "value", + "key2": "value", + } + + hcloud.fail_on_invalid_params(**kwargs) + if msg is None: + module.fail_json.assert_not_called() + else: + module.fail_json.assert_called_with(msg=msg)