From b0ae3f8a8db9ec3257879f2383e29a6287c92df4 Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Tue, 22 Jun 2021 20:37:30 +0530 Subject: [PATCH] test: Unit tests for validation methods (#75061) * check_required_one_of() * check_required_by() * check_required_if() * check_missing_parameters() Fixes: #55994 Signed-off-by: Abhijeet Kasurde --- changelogs/fragments/55994_testcases.yml | 3 + lib/ansible/module_utils/common/validation.py | 10 ++ .../test_check_missing_parameters.py | 35 +++++++ .../validation/test_check_required_by.py | 98 +++++++++++++++++++ .../validation/test_check_required_if.py | 79 +++++++++++++++ .../validation/test_check_required_one_of.py | 47 +++++++++ 6 files changed, 272 insertions(+) create mode 100644 changelogs/fragments/55994_testcases.yml create mode 100644 test/units/module_utils/common/validation/test_check_missing_parameters.py create mode 100644 test/units/module_utils/common/validation/test_check_required_by.py create mode 100644 test/units/module_utils/common/validation/test_check_required_if.py create mode 100644 test/units/module_utils/common/validation/test_check_required_one_of.py diff --git a/changelogs/fragments/55994_testcases.yml b/changelogs/fragments/55994_testcases.yml new file mode 100644 index 00000000000..a46bc755095 --- /dev/null +++ b/changelogs/fragments/55994_testcases.yml @@ -0,0 +1,3 @@ +--- +minor_changes: +- validation testcases for check_* APIs (https://github.com/ansible/ansible/issues/55994). diff --git a/lib/ansible/module_utils/common/validation.py b/lib/ansible/module_utils/common/validation.py index 7bb1add330b..5a4cebbc207 100644 --- a/lib/ansible/module_utils/common/validation.py +++ b/lib/ansible/module_utils/common/validation.py @@ -75,6 +75,8 @@ def check_mutually_exclusive(terms, parameters, options_context=None): :arg terms: List of mutually exclusive parameters :arg parameters: Dictionary of parameters + :kwarg options_context: List of strings of parent key names if ``terms`` are + in a sub spec. :returns: Empty list or raises :class:`TypeError` if the check fails. """ @@ -142,6 +144,8 @@ def check_required_together(terms, parameters, options_context=None): parameters that are all required when at least one is specified in the parameters. :arg parameters: Dictionary of parameters + :kwarg options_context: List of strings of parent key names if ``terms`` are + in a sub spec. :returns: Empty list or raises :class:`TypeError` if the check fails. """ @@ -174,6 +178,8 @@ def check_required_by(requirements, parameters, options_context=None): :arg requirements: Dictionary of requirements :arg parameters: Dictionary of parameters + :kwarg options_context: List of strings of parent key names if ``requirements`` are + in a sub spec. :returns: Empty dictionary or raises :class:`TypeError` if the """ @@ -213,6 +219,8 @@ def check_required_arguments(argument_spec, parameters, options_context=None): :arg argument_spec: Argument spec dictionary containing all parameters and their specification :arg parameters: Dictionary of parameters + :kwarg options_context: List of strings of parent key names if ``argument_spec`` are + in a sub spec. :returns: Empty list or raises :class:`TypeError` if the check fails. """ @@ -280,6 +288,8 @@ def check_required_if(requirements, parameters, options_context=None): } ] + :kwarg options_context: List of strings of parent key names if ``requirements`` are + in a sub spec. """ results = [] if requirements is None: diff --git a/test/units/module_utils/common/validation/test_check_missing_parameters.py b/test/units/module_utils/common/validation/test_check_missing_parameters.py new file mode 100644 index 00000000000..6cbcb8bff5c --- /dev/null +++ b/test/units/module_utils/common/validation/test_check_missing_parameters.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import pytest + +from ansible.module_utils._text import to_native +from ansible.module_utils.common.validation import check_required_one_of +from ansible.module_utils.common.validation import check_missing_parameters + + +@pytest.fixture +def arguments_terms(): + return {"path": ""} + + +def test_check_missing_parameters(): + assert check_missing_parameters([], {}) == [] + + +def test_check_missing_parameters_list(): + expected = "missing required arguments: path" + + with pytest.raises(TypeError) as e: + check_missing_parameters({}, ["path"]) + + assert to_native(e.value) == expected + + +def test_check_missing_parameters_positive(): + assert check_missing_parameters({"path": "/foo"}, ["path"]) == [] diff --git a/test/units/module_utils/common/validation/test_check_required_by.py b/test/units/module_utils/common/validation/test_check_required_by.py new file mode 100644 index 00000000000..62cccff3512 --- /dev/null +++ b/test/units/module_utils/common/validation/test_check_required_by.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import pytest + +from ansible.module_utils._text import to_native +from ansible.module_utils.common.validation import check_required_by + + +@pytest.fixture +def path_arguments_terms(): + return { + "path": ["mode", "owner"], + } + + +def test_check_required_by(): + arguments_terms = {} + params = {} + assert check_required_by(arguments_terms, params) == {} + + +def test_check_required_by_missing(): + arguments_terms = { + "force": "force_reason", + } + params = {"force": True} + expected = "missing parameter(s) required by 'force': force_reason" + + with pytest.raises(TypeError) as e: + check_required_by(arguments_terms, params) + + assert to_native(e.value) == expected + + +def test_check_required_by_multiple(path_arguments_terms): + params = { + "path": "/foo/bar", + } + expected = "missing parameter(s) required by 'path': mode, owner" + + with pytest.raises(TypeError) as e: + check_required_by(path_arguments_terms, params) + + assert to_native(e.value) == expected + + +def test_check_required_by_single(path_arguments_terms): + params = {"path": "/foo/bar", "mode": "0700"} + expected = "missing parameter(s) required by 'path': owner" + + with pytest.raises(TypeError) as e: + check_required_by(path_arguments_terms, params) + + assert to_native(e.value) == expected + + +def test_check_required_by_missing_none(path_arguments_terms): + params = { + "path": "/foo/bar", + "mode": "0700", + "owner": "root", + } + assert check_required_by(path_arguments_terms, params) + + +def test_check_required_by_options_context(path_arguments_terms): + params = {"path": "/foo/bar", "mode": "0700"} + + options_context = ["foo_context"] + + expected = "missing parameter(s) required by 'path': owner found in foo_context" + + with pytest.raises(TypeError) as e: + check_required_by(path_arguments_terms, params, options_context) + + assert to_native(e.value) == expected + + +def test_check_required_by_missing_multiple_options_context(path_arguments_terms): + params = { + "path": "/foo/bar", + } + options_context = ["foo_context"] + + expected = ( + "missing parameter(s) required by 'path': mode, owner found in foo_context" + ) + + with pytest.raises(TypeError) as e: + check_required_by(path_arguments_terms, params, options_context) + + assert to_native(e.value) == expected diff --git a/test/units/module_utils/common/validation/test_check_required_if.py b/test/units/module_utils/common/validation/test_check_required_if.py new file mode 100644 index 00000000000..5b4b7983d7b --- /dev/null +++ b/test/units/module_utils/common/validation/test_check_required_if.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import pytest + +from ansible.module_utils._text import to_native +from ansible.module_utils.common.validation import check_required_if + + +def test_check_required_if(): + arguments_terms = {} + params = {} + assert check_required_if(arguments_terms, params) == [] + + +def test_check_required_if_missing(): + arguments_terms = [["state", "present", ("path",)]] + params = {"state": "present"} + expected = "state is present but all of the following are missing: path" + + with pytest.raises(TypeError) as e: + check_required_if(arguments_terms, params) + + assert to_native(e.value) == expected + + +def test_check_required_if_missing_required(): + arguments_terms = [["state", "present", ("path", "owner"), True]] + params = {"state": "present"} + expected = "state is present but any of the following are missing: path, owner" + + with pytest.raises(TypeError) as e: + check_required_if(arguments_terms, params) + + assert to_native(e.value) == expected + + +def test_check_required_if_missing_multiple(): + arguments_terms = [["state", "present", ("path", "owner")]] + params = { + "state": "present", + } + expected = "state is present but all of the following are missing: path, owner" + + with pytest.raises(TypeError) as e: + check_required_if(arguments_terms, params) + + assert to_native(e.value) == expected + + +def test_check_required_if_missing_multiple(): + arguments_terms = [["state", "present", ("path", "owner")]] + params = { + "state": "present", + } + options_context = ["foo_context"] + expected = "state is present but all of the following are missing: path, owner found in foo_context" + + with pytest.raises(TypeError) as e: + check_required_if(arguments_terms, params, options_context) + + assert to_native(e.value) == expected + + +def test_check_required_if_multiple(): + arguments_terms = [["state", "present", ("path", "owner")]] + params = { + "state": "present", + "path": "/foo", + "owner": "root", + } + options_context = ["foo_context"] + assert check_required_if(arguments_terms, params) == [] + assert check_required_if(arguments_terms, params, options_context) == [] diff --git a/test/units/module_utils/common/validation/test_check_required_one_of.py b/test/units/module_utils/common/validation/test_check_required_one_of.py new file mode 100644 index 00000000000..b08188917d9 --- /dev/null +++ b/test/units/module_utils/common/validation/test_check_required_one_of.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import pytest + +from ansible.module_utils._text import to_native +from ansible.module_utils.common.validation import check_required_one_of + + +@pytest.fixture +def arguments_terms(): + return [["path", "owner"]] + + +def test_check_required_one_of(): + assert check_required_one_of([], {}) == [] + + +def test_check_required_one_of_missing(arguments_terms): + params = {"state": "present"} + expected = "one of the following is required: path, owner" + + with pytest.raises(TypeError) as e: + check_required_one_of(arguments_terms, params) + + assert to_native(e.value) == expected + + +def test_check_required_one_of_provided(arguments_terms): + params = {"state": "present", "path": "/foo"} + assert check_required_one_of(arguments_terms, params) == [] + + +def test_check_required_one_of_context(arguments_terms): + params = {"state": "present"} + expected = "one of the following is required: path, owner found in foo_context" + option_context = ["foo_context"] + + with pytest.raises(TypeError) as e: + check_required_one_of(arguments_terms, params, option_context) + + assert to_native(e.value) == expected