diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:21 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:21 +0000 |
commit | 8a754e0858d922e955e71b253c139e071ecec432 (patch) | |
tree | 527d16e74bfd1840c85efd675fdecad056c54107 /test/units/module_utils/common/arg_spec | |
parent | Initial commit. (diff) | |
download | ansible-core-8a754e0858d922e955e71b253c139e071ecec432.tar.xz ansible-core-8a754e0858d922e955e71b253c139e071ecec432.zip |
Adding upstream version 2.14.3.upstream/2.14.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
6 files changed, 765 insertions, 0 deletions
diff --git a/test/units/module_utils/common/arg_spec/__init__.py b/test/units/module_utils/common/arg_spec/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/units/module_utils/common/arg_spec/__init__.py diff --git a/test/units/module_utils/common/arg_spec/test_aliases.py b/test/units/module_utils/common/arg_spec/test_aliases.py new file mode 100644 index 0000000..7d30fb0 --- /dev/null +++ b/test/units/module_utils/common/arg_spec/test_aliases.py @@ -0,0 +1,132 @@ +# -*- 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.errors import AnsibleValidationError, AnsibleValidationErrorMultiple +from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult +from ansible.module_utils.common.warnings import get_deprecation_messages, get_warning_messages + +# id, argument spec, parameters, expected parameters, deprecation, warning +ALIAS_TEST_CASES = [ + ( + "alias", + {'path': {'aliases': ['dir', 'directory']}}, + {'dir': '/tmp'}, + { + 'dir': '/tmp', + 'path': '/tmp', + }, + "", + "", + ), + ( + "alias-duplicate-warning", + {'path': {'aliases': ['dir', 'directory']}}, + { + 'dir': '/tmp', + 'directory': '/tmp', + }, + { + 'dir': '/tmp', + 'directory': '/tmp', + 'path': '/tmp', + }, + "", + {'alias': 'directory', 'option': 'path'}, + ), + ( + "deprecated-alias", + { + 'path': { + 'aliases': ['not_yo_path'], + 'deprecated_aliases': [ + { + 'name': 'not_yo_path', + 'version': '1.7', + } + ] + } + }, + {'not_yo_path': '/tmp'}, + { + 'path': '/tmp', + 'not_yo_path': '/tmp', + }, + { + 'version': '1.7', + 'date': None, + 'collection_name': None, + 'msg': "Alias 'not_yo_path' is deprecated. See the module docs for more information", + }, + "", + ) +] + + +# id, argument spec, parameters, expected parameters, error +ALIAS_TEST_CASES_INVALID = [ + ( + "alias-invalid", + {'path': {'aliases': 'bad'}}, + {}, + {'path': None}, + "internal error: aliases must be a list or tuple", + ), + ( + # This isn't related to aliases, but it exists in the alias handling code + "default-and-required", + {'name': {'default': 'ray', 'required': True}}, + {}, + {'name': 'ray'}, + "internal error: required and default are mutually exclusive for name", + ), +] + + +@pytest.mark.parametrize( + ('arg_spec', 'parameters', 'expected', 'deprecation', 'warning'), + ((i[1:]) for i in ALIAS_TEST_CASES), + ids=[i[0] for i in ALIAS_TEST_CASES] +) +def test_aliases(arg_spec, parameters, expected, deprecation, warning): + v = ArgumentSpecValidator(arg_spec) + result = v.validate(parameters) + + assert isinstance(result, ValidationResult) + assert result.validated_parameters == expected + assert result.error_messages == [] + assert result._aliases == { + alias: param + for param, value in arg_spec.items() + for alias in value.get("aliases", []) + } + + if deprecation: + assert deprecation == result._deprecations[0] + else: + assert result._deprecations == [] + + if warning: + assert warning == result._warnings[0] + else: + assert result._warnings == [] + + +@pytest.mark.parametrize( + ('arg_spec', 'parameters', 'expected', 'error'), + ((i[1:]) for i in ALIAS_TEST_CASES_INVALID), + ids=[i[0] for i in ALIAS_TEST_CASES_INVALID] +) +def test_aliases_invalid(arg_spec, parameters, expected, error): + v = ArgumentSpecValidator(arg_spec) + result = v.validate(parameters) + + assert isinstance(result, ValidationResult) + assert error in result.error_messages + assert isinstance(result.errors.errors[0], AnsibleValidationError) + assert isinstance(result.errors, AnsibleValidationErrorMultiple) diff --git a/test/units/module_utils/common/arg_spec/test_module_validate.py b/test/units/module_utils/common/arg_spec/test_module_validate.py new file mode 100644 index 0000000..2c2211c --- /dev/null +++ b/test/units/module_utils/common/arg_spec/test_module_validate.py @@ -0,0 +1,58 @@ +# -*- 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 + +from ansible.module_utils.common import warnings + +from ansible.module_utils.common.arg_spec import ModuleArgumentSpecValidator, ValidationResult + + +def test_module_validate(): + arg_spec = {'name': {}} + parameters = {'name': 'larry'} + expected = {'name': 'larry'} + + v = ModuleArgumentSpecValidator(arg_spec) + result = v.validate(parameters) + + assert isinstance(result, ValidationResult) + assert result.error_messages == [] + assert result._deprecations == [] + assert result._warnings == [] + assert result.validated_parameters == expected + + +def test_module_alias_deprecations_warnings(monkeypatch): + monkeypatch.setattr(warnings, '_global_deprecations', []) + + arg_spec = { + 'path': { + 'aliases': ['source', 'src', 'flamethrower'], + 'deprecated_aliases': [{'name': 'flamethrower', 'date': '2020-03-04'}], + }, + } + parameters = {'flamethrower': '/tmp', 'source': '/tmp'} + expected = { + 'path': '/tmp', + 'flamethrower': '/tmp', + 'source': '/tmp', + } + + v = ModuleArgumentSpecValidator(arg_spec) + result = v.validate(parameters) + + assert result.validated_parameters == expected + assert result._deprecations == [ + { + 'collection_name': None, + 'date': '2020-03-04', + 'msg': "Alias 'flamethrower' is deprecated. See the module docs for more information", + 'version': None, + } + ] + assert "Alias 'flamethrower' is deprecated" in warnings._global_deprecations[0]['msg'] + assert result._warnings == [{'alias': 'flamethrower', 'option': 'path'}] + assert "Both option path and its alias flamethrower are set" in warnings._global_warnings[0] diff --git a/test/units/module_utils/common/arg_spec/test_sub_spec.py b/test/units/module_utils/common/arg_spec/test_sub_spec.py new file mode 100644 index 0000000..a6e7575 --- /dev/null +++ b/test/units/module_utils/common/arg_spec/test_sub_spec.py @@ -0,0 +1,106 @@ +# -*- 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 + +from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult + + +def test_sub_spec(): + arg_spec = { + 'state': {}, + 'user': { + 'type': 'dict', + 'options': { + 'first': {'no_log': True}, + 'last': {}, + 'age': {'type': 'int'}, + } + } + } + + parameters = { + 'state': 'present', + 'user': { + 'first': 'Rey', + 'last': 'Skywalker', + 'age': '19', + } + } + + expected = { + 'state': 'present', + 'user': { + 'first': 'Rey', + 'last': 'Skywalker', + 'age': 19, + } + } + + v = ArgumentSpecValidator(arg_spec) + result = v.validate(parameters) + + assert isinstance(result, ValidationResult) + assert result.validated_parameters == expected + assert result.error_messages == [] + + +def test_nested_sub_spec(): + arg_spec = { + 'type': {}, + 'car': { + 'type': 'dict', + 'options': { + 'make': {}, + 'model': {}, + 'customizations': { + 'type': 'dict', + 'options': { + 'engine': {}, + 'transmission': {}, + 'color': {}, + 'max_rpm': {'type': 'int'}, + } + } + } + } + } + + parameters = { + 'type': 'endurance', + 'car': { + 'make': 'Ford', + 'model': 'GT-40', + 'customizations': { + 'engine': '7.0 L', + 'transmission': '5-speed', + 'color': 'Ford blue', + 'max_rpm': '6000', + } + + } + } + + expected = { + 'type': 'endurance', + 'car': { + 'make': 'Ford', + 'model': 'GT-40', + 'customizations': { + 'engine': '7.0 L', + 'transmission': '5-speed', + 'color': 'Ford blue', + 'max_rpm': 6000, + } + + } + } + + v = ArgumentSpecValidator(arg_spec) + result = v.validate(parameters) + + assert isinstance(result, ValidationResult) + assert result.validated_parameters == expected + assert result.error_messages == [] diff --git a/test/units/module_utils/common/arg_spec/test_validate_invalid.py b/test/units/module_utils/common/arg_spec/test_validate_invalid.py new file mode 100644 index 0000000..7302e8a --- /dev/null +++ b/test/units/module_utils/common/arg_spec/test_validate_invalid.py @@ -0,0 +1,134 @@ +# -*- 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.common.arg_spec import ArgumentSpecValidator, ValidationResult +from ansible.module_utils.errors import AnsibleValidationErrorMultiple +from ansible.module_utils.six import PY2 + + +# Each item is id, argument_spec, parameters, expected, unsupported parameters, error test string +INVALID_SPECS = [ + ( + 'invalid-list', + {'packages': {'type': 'list'}}, + {'packages': {'key': 'value'}}, + {'packages': {'key': 'value'}}, + set(), + "unable to convert to list: <class 'dict'> cannot be converted to a list", + ), + ( + 'invalid-dict', + {'users': {'type': 'dict'}}, + {'users': ['one', 'two']}, + {'users': ['one', 'two']}, + set(), + "unable to convert to dict: <class 'list'> cannot be converted to a dict", + ), + ( + 'invalid-bool', + {'bool': {'type': 'bool'}}, + {'bool': {'k': 'v'}}, + {'bool': {'k': 'v'}}, + set(), + "unable to convert to bool: <class 'dict'> cannot be converted to a bool", + ), + ( + 'invalid-float', + {'float': {'type': 'float'}}, + {'float': 'hello'}, + {'float': 'hello'}, + set(), + "unable to convert to float: <class 'str'> cannot be converted to a float", + ), + ( + 'invalid-bytes', + {'bytes': {'type': 'bytes'}}, + {'bytes': 'one'}, + {'bytes': 'one'}, + set(), + "unable to convert to bytes: <class 'str'> cannot be converted to a Byte value", + ), + ( + 'invalid-bits', + {'bits': {'type': 'bits'}}, + {'bits': 'one'}, + {'bits': 'one'}, + set(), + "unable to convert to bits: <class 'str'> cannot be converted to a Bit value", + ), + ( + 'invalid-jsonargs', + {'some_json': {'type': 'jsonarg'}}, + {'some_json': set()}, + {'some_json': set()}, + set(), + "unable to convert to jsonarg: <class 'set'> cannot be converted to a json string", + ), + ( + 'invalid-parameter', + {'name': {}}, + { + 'badparam': '', + 'another': '', + }, + { + 'name': None, + 'badparam': '', + 'another': '', + }, + set(('another', 'badparam')), + "another, badparam. Supported parameters include: name.", + ), + ( + 'invalid-elements', + {'numbers': {'type': 'list', 'elements': 'int'}}, + {'numbers': [55, 33, 34, {'key': 'value'}]}, + {'numbers': [55, 33, 34]}, + set(), + "Elements value for option 'numbers' is of type <class 'dict'> and we were unable to convert to int: <class 'dict'> cannot be converted to an int" + ), + ( + 'required', + {'req': {'required': True}}, + {}, + {'req': None}, + set(), + "missing required arguments: req" + ), + ( + 'blank_values', + {'ch_param': {'elements': 'str', 'type': 'list', 'choices': ['a', 'b']}}, + {'ch_param': ['']}, + {'ch_param': ['']}, + set(), + "value of ch_param must be one or more of" + ) +] + + +@pytest.mark.parametrize( + ('arg_spec', 'parameters', 'expected', 'unsupported', 'error'), + (i[1:] for i in INVALID_SPECS), + ids=[i[0] for i in INVALID_SPECS] +) +def test_invalid_spec(arg_spec, parameters, expected, unsupported, error): + v = ArgumentSpecValidator(arg_spec) + result = v.validate(parameters) + + with pytest.raises(AnsibleValidationErrorMultiple) as exc_info: + raise result.errors + + if PY2: + error = error.replace('class', 'type') + + assert isinstance(result, ValidationResult) + assert error in exc_info.value.msg + assert error in result.error_messages[0] + assert result.unsupported_parameters == unsupported + assert result.validated_parameters == expected diff --git a/test/units/module_utils/common/arg_spec/test_validate_valid.py b/test/units/module_utils/common/arg_spec/test_validate_valid.py new file mode 100644 index 0000000..7e41127 --- /dev/null +++ b/test/units/module_utils/common/arg_spec/test_validate_valid.py @@ -0,0 +1,335 @@ +# -*- 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.common.arg_spec import ArgumentSpecValidator, ValidationResult + +# Each item is id, argument_spec, parameters, expected, valid parameter names +VALID_SPECS = [ + ( + 'str-no-type-specified', + {'name': {}}, + {'name': 'rey'}, + {'name': 'rey'}, + set(('name',)), + ), + ( + 'str', + {'name': {'type': 'str'}}, + {'name': 'rey'}, + {'name': 'rey'}, + set(('name',)), + ), + ( + 'str-convert', + {'name': {'type': 'str'}}, + {'name': 5}, + {'name': '5'}, + set(('name',)), + ), + ( + 'list', + {'packages': {'type': 'list'}}, + {'packages': ['vim', 'python']}, + {'packages': ['vim', 'python']}, + set(('packages',)), + ), + ( + 'list-comma-string', + {'packages': {'type': 'list'}}, + {'packages': 'vim,python'}, + {'packages': ['vim', 'python']}, + set(('packages',)), + ), + ( + 'list-comma-string-space', + {'packages': {'type': 'list'}}, + {'packages': 'vim, python'}, + {'packages': ['vim', ' python']}, + set(('packages',)), + ), + ( + 'dict', + {'user': {'type': 'dict'}}, + { + 'user': + { + 'first': 'rey', + 'last': 'skywalker', + } + }, + { + 'user': + { + 'first': 'rey', + 'last': 'skywalker', + } + }, + set(('user',)), + ), + ( + 'dict-k=v', + {'user': {'type': 'dict'}}, + {'user': 'first=rey,last=skywalker'}, + { + 'user': + { + 'first': 'rey', + 'last': 'skywalker', + } + }, + set(('user',)), + ), + ( + 'dict-k=v-spaces', + {'user': {'type': 'dict'}}, + {'user': 'first=rey, last=skywalker'}, + { + 'user': + { + 'first': 'rey', + 'last': 'skywalker', + } + }, + set(('user',)), + ), + ( + 'bool', + { + 'enabled': {'type': 'bool'}, + 'disabled': {'type': 'bool'}, + }, + { + 'enabled': True, + 'disabled': False, + }, + { + 'enabled': True, + 'disabled': False, + }, + set(('enabled', 'disabled')), + ), + ( + 'bool-ints', + { + 'enabled': {'type': 'bool'}, + 'disabled': {'type': 'bool'}, + }, + { + 'enabled': 1, + 'disabled': 0, + }, + { + 'enabled': True, + 'disabled': False, + }, + set(('enabled', 'disabled')), + ), + ( + 'bool-true-false', + { + 'enabled': {'type': 'bool'}, + 'disabled': {'type': 'bool'}, + }, + { + 'enabled': 'true', + 'disabled': 'false', + }, + { + 'enabled': True, + 'disabled': False, + }, + set(('enabled', 'disabled')), + ), + ( + 'bool-yes-no', + { + 'enabled': {'type': 'bool'}, + 'disabled': {'type': 'bool'}, + }, + { + 'enabled': 'yes', + 'disabled': 'no', + }, + { + 'enabled': True, + 'disabled': False, + }, + set(('enabled', 'disabled')), + ), + ( + 'bool-y-n', + { + 'enabled': {'type': 'bool'}, + 'disabled': {'type': 'bool'}, + }, + { + 'enabled': 'y', + 'disabled': 'n', + }, + { + 'enabled': True, + 'disabled': False, + }, + set(('enabled', 'disabled')), + ), + ( + 'bool-on-off', + { + 'enabled': {'type': 'bool'}, + 'disabled': {'type': 'bool'}, + }, + { + 'enabled': 'on', + 'disabled': 'off', + }, + { + 'enabled': True, + 'disabled': False, + }, + set(('enabled', 'disabled')), + ), + ( + 'bool-1-0', + { + 'enabled': {'type': 'bool'}, + 'disabled': {'type': 'bool'}, + }, + { + 'enabled': '1', + 'disabled': '0', + }, + { + 'enabled': True, + 'disabled': False, + }, + set(('enabled', 'disabled')), + ), + ( + 'bool-float', + { + 'enabled': {'type': 'bool'}, + 'disabled': {'type': 'bool'}, + }, + { + 'enabled': 1.0, + 'disabled': 0.0, + }, + { + 'enabled': True, + 'disabled': False, + }, + set(('enabled', 'disabled')), + ), + ( + 'float', + {'digit': {'type': 'float'}}, + {'digit': 3.14159}, + {'digit': 3.14159}, + set(('digit',)), + ), + ( + 'float-str', + {'digit': {'type': 'float'}}, + {'digit': '3.14159'}, + {'digit': 3.14159}, + set(('digit',)), + ), + ( + 'path', + {'path': {'type': 'path'}}, + {'path': '~/bin'}, + {'path': '/home/ansible/bin'}, + set(('path',)), + ), + ( + 'raw', + {'raw': {'type': 'raw'}}, + {'raw': 0x644}, + {'raw': 0x644}, + set(('raw',)), + ), + ( + 'bytes', + {'bytes': {'type': 'bytes'}}, + {'bytes': '2K'}, + {'bytes': 2048}, + set(('bytes',)), + ), + ( + 'bits', + {'bits': {'type': 'bits'}}, + {'bits': '1Mb'}, + {'bits': 1048576}, + set(('bits',)), + ), + ( + 'jsonarg', + {'some_json': {'type': 'jsonarg'}}, + {'some_json': '{"users": {"bob": {"role": "accountant"}}}'}, + {'some_json': '{"users": {"bob": {"role": "accountant"}}}'}, + set(('some_json',)), + ), + ( + 'jsonarg-list', + {'some_json': {'type': 'jsonarg'}}, + {'some_json': ['one', 'two']}, + {'some_json': '["one", "two"]'}, + set(('some_json',)), + ), + ( + 'jsonarg-dict', + {'some_json': {'type': 'jsonarg'}}, + {'some_json': {"users": {"bob": {"role": "accountant"}}}}, + {'some_json': '{"users": {"bob": {"role": "accountant"}}}'}, + set(('some_json',)), + ), + ( + 'defaults', + {'param': {'default': 'DEFAULT'}}, + {}, + {'param': 'DEFAULT'}, + set(('param',)), + ), + ( + 'elements', + {'numbers': {'type': 'list', 'elements': 'int'}}, + {'numbers': [55, 33, 34, '22']}, + {'numbers': [55, 33, 34, 22]}, + set(('numbers',)), + ), + ( + 'aliases', + {'src': {'aliases': ['path', 'source']}}, + {'src': '/tmp'}, + {'src': '/tmp'}, + set(('src (path, source)',)), + ) +] + + +@pytest.mark.parametrize( + ('arg_spec', 'parameters', 'expected', 'valid_params'), + (i[1:] for i in VALID_SPECS), + ids=[i[0] for i in VALID_SPECS] +) +def test_valid_spec(arg_spec, parameters, expected, valid_params, mocker): + mocker.patch('ansible.module_utils.common.validation.os.path.expanduser', return_value='/home/ansible/bin') + mocker.patch('ansible.module_utils.common.validation.os.path.expandvars', return_value='/home/ansible/bin') + + v = ArgumentSpecValidator(arg_spec) + result = v.validate(parameters) + + assert isinstance(result, ValidationResult) + assert result.validated_parameters == expected + assert result.unsupported_parameters == set() + assert result.error_messages == [] + assert v._valid_parameter_names == valid_params + + # Again to check caching + assert v._valid_parameter_names == valid_params |