summaryrefslogtreecommitdiffstats
path: root/test/units/module_utils/common/arg_spec
diff options
context:
space:
mode:
Diffstat (limited to 'test/units/module_utils/common/arg_spec')
-rw-r--r--test/units/module_utils/common/arg_spec/__init__.py0
-rw-r--r--test/units/module_utils/common/arg_spec/test_aliases.py132
-rw-r--r--test/units/module_utils/common/arg_spec/test_module_validate.py58
-rw-r--r--test/units/module_utils/common/arg_spec/test_sub_spec.py106
-rw-r--r--test/units/module_utils/common/arg_spec/test_validate_invalid.py134
-rw-r--r--test/units/module_utils/common/arg_spec/test_validate_valid.py335
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