diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/sensu/sensu_go/tests/unit | |
parent | Initial commit. (diff) | |
download | ansible-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/sensu/sensu_go/tests/unit')
60 files changed, 8175 insertions, 0 deletions
diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/action/test_bonsai_asset.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/action/test_bonsai_asset.py new file mode 100644 index 000000000..100b30c6b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/action/test_bonsai_asset.py @@ -0,0 +1,316 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible.playbook.task import Task + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + bonsai, errors, +) +from ansible_collections.sensu.sensu_go.plugins.action import bonsai_asset + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestValidate: + @pytest.mark.parametrize("name,args,required,typ", [ + # Required values must match the selected type. + ("a", dict(a=3), True, int), + ("a", dict(a=3.3), True, float), + ("a", dict(a="b"), True, str), + ("a", dict(a=[]), True, list), + ("a", dict(a={}), True, dict), + # Optional values are not checked for type-correctness if they are + # missing. + ("a", dict(), False, int), + ("a", dict(), False, float), + ("a", dict(), False, str), + ("a", dict(), False, list), + ("a", dict(), False, dict), + ]) + def test_valid_values(self, name, args, required, typ): + bonsai_asset.validate(name, args, required, typ) + + def test_missing_required(self): + with pytest.raises(errors.Error, match="required"): + bonsai_asset.validate("a", {}, True, str) + + def test_invalid_type(self): + with pytest.raises(errors.Error, match="should"): + bonsai_asset.validate("a", dict(a=3), True, str) + + def test_invalid_type_for_optional_value(self): + with pytest.raises(errors.Error, match="should"): + bonsai_asset.validate("a", dict(a=3), False, dict) + + +class TestValidateArguments: + def test_valid_minimal_args(self): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", + )) + + def test_valid_all_args(self): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", rename="def", + labels={}, annotations={}, + )) + + def test_valid_unicode_strings_python2(self): + bonsai_asset.ActionModule.validate_arguments(dict( + name=u"abc", version=u"1.2.3", rename=u"def", + labels={}, annotations={}, + )) + + def test_invalid_name(self): + with pytest.raises(errors.Error, match="name"): + bonsai_asset.ActionModule.validate_arguments(dict( + name=1.234, version="1.2.3", + )) + + def test_missing_name(self): + with pytest.raises(errors.Error, match="name"): + bonsai_asset.ActionModule.validate_arguments(dict( + version="1.2.3", + )) + + def test_invalid_version(self): + with pytest.raises(errors.Error, match="version"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version=1.2, + )) + + def test_missing_version(self): + with pytest.raises(errors.Error, match="version"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", + )) + + def test_invalid_rename(self): + with pytest.raises(errors.Error, match="rename"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", rename=1, + )) + + def test_invalid_labels(self): + with pytest.raises(errors.Error, match="labels"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", labels=1, + )) + + def test_invalid_annotations(self): + with pytest.raises(errors.Error, match="annotations"): + bonsai_asset.ActionModule.validate_arguments(dict( + name="abc", version="1.2.3", annotations=1, + )) + + +class TestBuildAssetArgs: + def test_no_additional_metadata(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict(name="test/asset", version="1.2.3"), + dict(builds=[], labels=None, annotations=None), + ) + + assert result == dict( + name="test/asset", + state="present", + builds=[], + ) + + def test_bonsai_metadata_only(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict(name="test/asset", version="1.2.3"), + dict(builds=[], labels=dict(a="b"), annotations=dict(c="d")), + ) + + assert result == dict( + name="test/asset", + state="present", + builds=[], + annotations=dict(c="d"), + labels=dict(a="b"), + ) + + def test_user_metadata_only(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict( + name="test/asset", + version="1.2.3", + labels=dict(my="label"), + annotations=dict(my="annotation"), + ), + dict(builds=[1, 2, 3], labels=None, annotations=None), + ) + + assert result == dict( + name="test/asset", + state="present", + builds=[1, 2, 3], + annotations=dict(my="annotation"), + labels=dict(my="label"), + ) + + def test_mixed_metadata(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict( + name="test/asset", + version="1.2.3", + labels=dict(my="label"), + annotations=dict(my="annotation"), + ), + dict(builds=[], labels=dict(my="x", a="b"), annotations=dict(my="c")), + ) + + assert result == dict( + name="test/asset", + state="present", + builds=[], + annotations=dict(my="annotation"), + labels=dict(my="label", a="b"), + ) + + def test_rename(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict(name="test/asset", version="1.2.3", rename="my-asset"), + dict(builds=[], labels=None, annotations=None), + ) + + assert result == dict( + name="my-asset", + state="present", + builds=[], + ) + + def test_auth_passthrough(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict( + auth=dict(url="http://localhost:1234"), + name="test/asset", + version="1.2.3", + ), + dict(builds=[], labels=None, annotations=None), + ) + + assert result == dict( + auth=dict(url="http://localhost:1234"), + name="test/asset", + state="present", + builds=[], + ) + + def test_namespace_passthrough(self): + result = bonsai_asset.ActionModule.build_asset_args( + dict(namespace='default', name="test/asset", version="1.2.3"), + dict(builds=[], labels=None, annotations=None), + ) + + assert result == dict( + name="test/asset", + namespace='default', + state="present", + builds=[], + ) + + +class TestDownloadAssetDefinition: + def get_mock_action(self, mocker, result): + action = bonsai_asset.ActionModule( + mocker.MagicMock(), mocker.MagicMock(), mocker.MagicMock(), loader=None, + templar=None, shared_loader_obj=None, + ) + action._execute_module = mocker.MagicMock(return_value=result) + return action + + def test_download_on_control_node(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.return_value = dict(sample="value") + action = self.get_mock_action(mocker, {}) + + result = action.download_asset_definition( + on_remote=False, name="test/asset", version="1.2.3", task_vars=None, + ) + + assert result == dict(sample="value") + bonsai_params.assert_called_once() + action._execute_module.assert_not_called() + + def test_fail_download_on_control_node(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.side_effect = errors.BonsaiError("Bonsai bad") + action = self.get_mock_action(mocker, {}) + + with pytest.raises(errors.Error, match="Bonsai bad"): + action.download_asset_definition( + on_remote=False, name="test/asset", version="1.2.3", task_vars=None, + ) + + bonsai_params.assert_called_once() + action._execute_module.assert_not_called() + + def test_download_on_target_node(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + action = self.get_mock_action(mocker, dict(asset="sample")) + + result = action.download_asset_definition( + on_remote=True, name="test/asset", version="1.2.3", task_vars=None, + ) + + assert result == "sample" + bonsai_params.assert_not_called() + action._execute_module.assert_called_once() + + def test_fail_on_target_node(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + action = self.get_mock_action(mocker, dict(failed=True, msg="Bad err")) + + with pytest.raises(errors.Error, match="Bad err"): + action.download_asset_definition( + on_remote=True, name="test/asset", version="1.2.3", task_vars=None, + ) + + bonsai_params.assert_not_called() + action._execute_module.assert_called_once() + + +class TestRun: + def test_success(self, mocker): + task = mocker.MagicMock(Task, async_val=0, args=dict( + name="test/asset", + version="1.2.3", + )) + action = bonsai_asset.ActionModule( + task, mocker.MagicMock(), mocker.MagicMock(), loader=None, + templar=None, shared_loader_obj=None, + ) + action._execute_module = mocker.MagicMock(return_value=dict(a=3)) + action.download_asset_definition = mocker.MagicMock( + return_value=dict(builds=[], labels=None, annotations=None), + ) + + result = action.run() + + assert result == dict(a=3) + + def test_fail(self, mocker): + task = mocker.MagicMock(Task, async_val=0, args=dict( + name="test/asset", + )) + action = bonsai_asset.ActionModule( + task, mocker.MagicMock(), mocker.MagicMock(), loader=None, + templar=None, shared_loader_obj=None, + ) + action._execute_module = mocker.MagicMock(return_value=dict(a=3)) + action.download_asset_definition = mocker.MagicMock( + return_value=dict(builds=[], labels=None, annotations=None), + ) + + result = action.run() + + assert result["failed"] is True diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_backends.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_backends.py new file mode 100644 index 000000000..261080e3b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_backends.py @@ -0,0 +1,54 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.filter import backends + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestBackends: + def test_backends_in_groups_no_ssl(self): + hostvars = { + "1.2.3.4": {"inventory_hostname": "1.2.3.4"}, + "1.2.3.5": {"inventory_hostname": "1.2.3.5"}, + "1.2.3.6": {"inventory_hostname": "1.2.3.6"}, + "1.2.3.7": {"inventory_hostname": "1.2.3.7"}, + } + groups = {"backends": ["1.2.3.4", "1.2.3.5"]} + + assert backends.backends(hostvars, groups) == [ + "ws://1.2.3.4:8081", + "ws://1.2.3.5:8081", + ] + + def test_backends_in_groups_ssl(self): + hostvars = { + "1.2.3.4": {"inventory_hostname": "1.2.3.4"}, + "1.2.3.5": {"inventory_hostname": "1.2.3.5"}, + "1.2.3.6": { + "inventory_hostname": "1.2.3.6", + "api_key_file": "path/to/key.file", + }, + "1.2.3.7": {"inventory_hostname": "1.2.3.7"}, + } + groups = {"backends": ["1.2.3.6"]} + + assert backends.backends(hostvars, groups) == ["wss://1.2.3.6:8081"] + + def test_backends_not_in_groups(self): + hostvars = { + "1.2.3.4": {"inventory_hostname": "1.2.3.4"}, + "1.2.3.5": {"inventory_hostname": "1.2.3.5"}, + "1.2.3.6": {"inventory_hostname": "1.2.3.6"}, + "1.2.3.7": {"inventory_hostname": "1.2.3.7"}, + } + groups = {} + + assert backends.backends(hostvars, groups) == [] diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_package_name.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_package_name.py new file mode 100644 index 000000000..794727abf --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/filter/test_package_name.py @@ -0,0 +1,58 @@ +# Copyright: (c) 2020, XLAB Steampunk <steampunk@xlab.si> +# +# 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 sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.filter import package_name + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestPackageName: + def test_yum_latest_version(self): + assert "package" == package_name.package_name( + "yum", "package", "latest", "latest", + ) + + def test_yum_latest_build(self): + assert "package-123" == package_name.package_name( + "yum", "package", "123", "latest", + ) + + def test_yum_selected_build(self): + assert "package-123-456" == package_name.package_name( + "yum", "package", "123", "456", + ) + + def test_yum_ignore_build_if_latest_version(self): + assert "package" == package_name.package_name( + "yum", "package", "latest", "456", + ) + + def test_apt_latest_version(self): + assert "package" == package_name.package_name( + "apt", "package", "latest", "latest", + ) + + def test_apt_latest_build(self): + assert "package=123-*" == package_name.package_name( + "apt", "package", "123", "latest", + ) + + def test_apt_selected_build(self): + assert "package=123-456" == package_name.package_name( + "apt", "package", "123", "456", + ) + + def test_apt_ignore_build_if_latest_version(self): + assert "package" == package_name.package_name( + "apt", "package", "latest", "456", + ) diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_arguments.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_arguments.py new file mode 100644 index 000000000..ae62f1f57 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_arguments.py @@ -0,0 +1,158 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + arguments, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestGetSpec: + @pytest.mark.parametrize("param", [ + "auth", "state", "name", "labels", "annotations", + ]) + def test_valid_parameter(self, param): + assert set(arguments.get_spec(param).keys()) == set((param,)) + + def test_invalid_parameter(self): + with pytest.raises(KeyError): + arguments.get_spec("bad_parameter_name") + + def test_multiple_parameters(self): + assert set(arguments.get_spec("auth", "name", "labels").keys()) == set( + ("auth", "name", "labels") + ) + + +class TestGetSpecPayload: + def test_no_key(self): + params = dict( + name="name", + key="value" + ) + + assert arguments.get_spec_payload(params) == dict() + + def test_spec_payload(self): + params = dict( + name="name", + key="value", + ) + + assert arguments.get_spec_payload(params, "key") == dict( + key="value", + ) + + +class TestGetRenamedSpecPayload: + def test_no_mapping(self): + params = dict( + name="name", + key="value", + ) + assert arguments.get_renamed_spec_payload(params, dict()) == dict() + + def test_renamed_payload(self): + params = dict( + name="name", + key="value", + ) + mapping = dict( + name="new_name", + ) + assert arguments.get_renamed_spec_payload(params, mapping) == dict( + new_name="name", + ) + + +class TestGetMutationPayload: + def test_name_only(self): + params = dict( + name="name", + ) + + assert arguments.get_mutation_payload(params) == dict( + metadata=dict( + name="name", + ), + ) + + def test_name_and_namespace(self): + params = dict( + name="name", + namespace="space", + ) + + assert arguments.get_mutation_payload(params) == dict( + metadata=dict( + name="name", + namespace="space", + ), + ) + + def test_wanted_key(self): + params = dict( + name="name", + key="value", + ) + + assert arguments.get_mutation_payload(params, "key") == dict( + key="value", + metadata=dict( + name="name", + ), + ) + + def test_namespace_is_none(self): + params = dict( + name="name", + namespace=None, + ) + + with pytest.raises(AssertionError, match="BUG"): + arguments.get_mutation_payload(params) + + def test_labels(self): + params = dict( + name="name", + labels=dict( + some="label", + numeric=3, + ), + ) + + assert arguments.get_mutation_payload(params) == dict( + metadata=dict( + name="name", + labels=dict( + some="label", + numeric="3", + ), + ), + ) + + def test_annotations(self): + params = dict( + name="name", + annotations=dict( + my="Annotation", + number=45, + ), + ) + + assert arguments.get_mutation_payload(params) == dict( + metadata=dict( + name="name", + annotations=dict( + my="Annotation", + number="45", + ), + ), + ) diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_bonsai.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_bonsai.py new file mode 100644 index 000000000..cf0d8a492 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_bonsai.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + bonsai, errors, http, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestGet: + def test_url_construction(self, mocker): + http_mock = mocker.patch.object(bonsai, "http") + http_mock.request.return_value = http.Response(200, "{}") + + bonsai.get("path") + + assert http_mock.request.call_args[0] == ( + "GET", "https://bonsai.sensu.io/api/v1/assets/path", + ) + + def test_bad_status(self, mocker): + http_mock = mocker.patch.object(bonsai, "http") + http_mock.request.return_value = http.Response(400, "{}") + + with pytest.raises(errors.BonsaiError, match="400"): + bonsai.get("path") + + def test_invalid_json(self, mocker): + http_mock = mocker.patch.object(bonsai, "http") + http_mock.request.return_value = http.Response(200, "{ a }") + + with pytest.raises(errors.BonsaiError, match="JSON"): + bonsai.get("path") + + +class TestGetAvailableAssetVersions: + def test_valid_data(self, mocker): + get = mocker.patch.object(bonsai, "get") + get.return_value = dict( + versions=[ + dict(version="1.2.3", assets=[]), + dict(version="1.2.4", assets=[]), + dict(version="1.2.5", assets=[]), + ], + ) + + result = bonsai.get_available_asset_versions("namespace", "name") + + assert set(("1.2.3", "1.2.4", "1.2.5")) == result + assert get.call_args[0] == ("namespace/name",) + + @pytest.mark.parametrize("data", [ + "invalid", + dict(invalid="toplevel"), + dict(versions="oh-no"), + dict(versions=["not", "ok"]), + dict(versions=[dict(invalid="internal")]), + ]) + def test_invalid_data(self, mocker, data): + get = mocker.patch.object(bonsai, "get") + get.return_value = data + + with pytest.raises(errors.BonsaiError, match="versions"): + bonsai.get_available_asset_versions("namespace", "name") + + +class TestGetAssetVersionBuilds: + def test_url_construction(self, mocker): + get = mocker.patch.object(bonsai, "get") + get.return_value = dict(spec=dict(builds=[])) + + bonsai.get_asset_version_builds("x", "y", "z") + + assert get.call_args[0] == ("x/y/z/release_asset_builds",) + + @pytest.mark.parametrize("data", [ + "invalid", + dict(missing="spec"), + dict(spec="invalid"), + dict(spec=dict(missing="builds")), + ]) + def test_invalid_data(self, mocker, data): + get = mocker.patch.object(bonsai, "get") + get.return_value = data + + with pytest.raises(errors.BonsaiError, match="spec"): + bonsai.get_asset_version_builds("x", "y", "z") + + +class TestGetAssetParameters: + def test_valid_all_data(self, mocker): + versions = mocker.patch.object(bonsai, "get_available_asset_versions") + versions.return_value = set(("t", "u", "v")) + builds = mocker.patch.object(bonsai, "get_asset_version_builds") + builds.return_value = dict( + metadata=dict( + annotations=dict(annotation="value"), + labels=dict(label="value"), + ), + spec=dict(builds=[1, 2, 3]), + ) + + result = bonsai.get_asset_parameters("x/y", "v") + + assert result == dict( + labels=dict(label="value"), + annotations=dict(annotation="value"), + builds=[1, 2, 3], + ) + assert versions.call_args[0] == ("x", "y") + assert builds.call_args[0] == ("x", "y", "v") + + def test_valid_minimal_data(self, mocker): + versions = mocker.patch.object(bonsai, "get_available_asset_versions") + versions.return_value = set(("t", "u", "v")) + builds = mocker.patch.object(bonsai, "get_asset_version_builds") + builds.return_value = dict( + spec=dict(builds=[1, 2, 3]), + ) + + result = bonsai.get_asset_parameters("x/y", "v") + + assert result == dict( + labels=None, + annotations=None, + builds=[1, 2, 3], + ) + assert versions.call_args[0] == ("x", "y") + assert builds.call_args[0] == ("x", "y", "v") + + def test_invalid_name(self, mocker): + with pytest.raises(errors.BonsaiError, match="names"): + bonsai.get_asset_parameters("x.y", "v") + + def test_invalid_version(self, mocker): + versions = mocker.patch.object(bonsai, "get_available_asset_versions") + versions.return_value = set(("t", "u")) + + with pytest.raises(errors.BonsaiError, match="Version"): + bonsai.get_asset_parameters("x/y", "v") diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_client.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_client.py new file mode 100644 index 000000000..ecc6fc54b --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_client.py @@ -0,0 +1,295 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + client, errors, http +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestAuthHeader: + def test_using_valid_token(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, '{"access_token": "token"}') + + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + + assert dict(Authorization="Bearer token") == c.auth_header + assert 1 == request.call_count + assert ("GET", "http://example.com/auth") == request.call_args[0] + assert "user" == request.call_args[1]["url_username"] + assert "pass" == request.call_args[1]["url_password"] + + def test_cache_auth_headers_with_token(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, '{"access_token": "token"}') + + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + for i in range(5): + c.auth_header + + assert 1 == request.call_count + + def test_login_failure_token_bad_status(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(500, '{"access_token": "token"}') + + with pytest.raises(errors.SensuError, match="500"): + client.Client( + "http://example.com/", "user", "pass", None, True, None, + ).auth_header + + def test_login_failure_token_bad_json(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, "{ not a json }") + + with pytest.raises(errors.SensuError, match="JSON"): + client.Client( + "http://example.com/", "user", "pass", None, True, None, + ).auth_header + + def test_login_failure_token_missing_token(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, '{"access_bla": "token"}') + + with pytest.raises(errors.SensuError, match="token"): + client.Client( + "http://example.com/", "user", "pass", None, True, None, + ).auth_header + + +class TestVersion: + def test_valid_version(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response( + 200, '{"sensu_backend":"5.21.0#sha-here"}', + ) + + assert c.version == "5.21.0" + + def test_valid_version_is_cached(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + get = mocker.patch.object(c, "get") + get.return_value = http.Response( + 200, '{"sensu_backend":"5.21.0#sha-here"}', + ) + + for i in range(4): + c.version + + get.assert_called_once() + + def test_non_200_response(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response( + 400, '{"sensu_backend":"5.21.0#sha-here"}', + ) + + with pytest.raises(errors.SensuError, match="400"): + c.version + + def test_bad_json_response(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response( + 200, '"sensu_backend', + ) + + with pytest.raises(errors.SensuError, match="JSON"): + c.version + + def test_missing_backend_version_in_response(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response(200, '{}') + + with pytest.raises(errors.SensuError, match="backend"): + c.version + + def test_invalid_version(self, mocker): + c = client.Client("http://example.com/", "u", "p", None, True, None) + mocker.patch.object(c, "get").return_value = http.Response( + 200, '{"sensu_backend":"devel"}', + ) + + assert c.version == c.BAD_VERSION + + +class TestRequest: + def test_request_payload_token(self, mocker): + request = mocker.patch.object(http, "request") + request.side_effect = ( + http.Response(200, '{"access_token": "token"}'), + http.Response(200, "data"), + ) + + client.Client( + "http://example.com/", "user", "pass", None, True, None, + ).request("PUT", "/path", dict(some="payload")) + + request.assert_called_with( + "PUT", "http://example.com/path", + payload=dict(some="payload"), + headers=dict(Authorization="Bearer token"), + validate_certs=True, + ca_path=None, + ) + + def test_request_payload_api_key(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, "data") + + client.Client( + "http://example.com/", None, None, "key", False, None, + ).request("PUT", "/path", dict(some="payload")) + + request.assert_called_once_with( + "PUT", "http://example.com/path", + payload=dict(some="payload"), + headers=dict(Authorization="Key key"), + validate_certs=False, + ca_path=None, + ) + + def test_request_no_payload_token(self, mocker): + request = mocker.patch.object(http, "request") + request.side_effect = ( + http.Response(200, '{"access_token": "token"}'), + http.Response(200, "data"), + ) + + client.Client( + "http://example.com/", "user", "pass", None, True, "/ca", + ).request("PUT", "/path") + + request.assert_called_with( + "PUT", "http://example.com/path", payload=None, + headers=dict(Authorization="Bearer token"), + validate_certs=True, + ca_path="/ca", + ) + + def test_request_no_payload_api_key(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, "data") + + client.Client( + "http://example.com/", "u", "p", "key", False, "/ca", + ).request("PUT", "/path") + + request.assert_called_once_with( + "PUT", "http://example.com/path", payload=None, + headers=dict(Authorization="Key key"), + validate_certs=False, + ca_path="/ca", + ) + + @pytest.mark.parametrize("status", [401, 403]) + def test_request_bad_credentials(self, status, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(status, "data") + + with pytest.raises(errors.SensuError, match="credentials"): + client.Client( + "http://example.com/", None, None, "key", True, None, + ).request("PUT", "/path", dict(some="payload")) + + request.assert_called_once_with( + "PUT", "http://example.com/path", + payload=dict(some="payload"), + headers=dict(Authorization="Key key"), + validate_certs=True, + ca_path=None, + ) + + +class TestGet: + def test_get(self, mocker): + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + c.request = mocker.Mock() + + c.get("/path") + + c.request.assert_called_with("GET", "/path") + + +class TestPut: + def test_put(self, mocker): + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + c.request = mocker.Mock() + + c.put("/path", {}) + + c.request.assert_called_with("PUT", "/path", {}) + + +class TestDelete: + def test_delete(self, mocker): + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + c.request = mocker.Mock() + + c.delete("/path") + + c.request.assert_called_with("DELETE", "/path") + + +class TestValidateAuthData: + def test_valid_creds(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(200, None) + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + + result = c.validate_auth_data("check_user", "check_pass") + + assert result + assert 1 == request.call_count + assert ("GET", "http://example.com/auth/test") == request.call_args[0] + assert "check_user" == request.call_args[1]["url_username"] + assert "check_pass" == request.call_args[1]["url_password"] + + def test_invalid_creds(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(401, None) + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + + result = c.validate_auth_data("check_user", "check_pass") + + assert not result + assert 1 == request.call_count + assert ("GET", "http://example.com/auth/test") == request.call_args[0] + assert "check_user" == request.call_args[1]["url_username"] + assert "check_pass" == request.call_args[1]["url_password"] + + def test_broken_backend(self, mocker): + request = mocker.patch.object(http, "request") + request.return_value = http.Response(500, None) + c = client.Client( + "http://example.com/", "user", "pass", None, True, None, + ) + + with pytest.raises(errors.SensuError, match="500"): + c.validate_auth_data("check_user", "check_pass") diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_http.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_http.py new file mode 100644 index 000000000..84fddb53a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_http.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 ssl +import sys + +import pytest + +from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestResponse: + def test_with_valid_json(self): + resp = http.Response(201, '{"some": ["json", "data", 3]}') + + assert 201 == resp.status + assert '{"some": ["json", "data", 3]}' == resp.data + assert {"some": ["json", "data", 3]} == resp.json + + def test_with_invalid_json(self): + resp = http.Response(404, "") + + assert 404 == resp.status + assert "" == resp.data + assert resp.json is None + + +class TestRequest: + def test_ok_request(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + resp = http.request("GET", "example.com/path") + + assert 200 == resp.status + assert "data" == resp.data + assert "GET" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + + def test_non_20x_status(self, mocker): + open_url = mocker.patch.object(http, "open_url") + open_url.side_effect = HTTPError( + "url", 404, "missing", {}, None, + ) + + resp = http.request("GET", "example.com/bad") + + assert 404 == resp.status + assert "missing" == resp.data + assert "GET" == open_url.call_args[1]["method"] + assert "example.com/bad" == open_url.call_args[1]["url"] + + def test_url_error(self, mocker): + open_url = mocker.patch.object(http, "open_url") + open_url.side_effect = URLError("Invalid") + + with pytest.raises(errors.HttpError): + http.request("GET", "example.com/bad") + + def test_payload_no_headers(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request("PUT", "example.com/path", payload=dict(a=2)) + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert '{"a":2}' == open_url.call_args[1]["data"] + headers = open_url.call_args[1]["headers"] + assert {"content-type": "application/json"} == headers + + def test_payload_with_headers(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request( + "PUT", "example.com/path", payload=dict(b=4), headers=dict(h="v"), + ) + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert '{"b":4}' == open_url.call_args[1]["data"] + headers = open_url.call_args[1]["headers"] + assert {"content-type": "application/json", "h": "v"} == headers + + def test_payload_overrides_data(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request( + "PUT", "example.com/path", payload=dict(a=2), data="data", + ) + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert '{"a":2}' == open_url.call_args[1]["data"] + headers = open_url.call_args[1]["headers"] + assert {"content-type": "application/json"} == headers + + def test_data(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request("PUT", "example.com/path", data="data") + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert "data" == open_url.call_args[1]["data"] + assert open_url.call_args[1]["headers"] is None + + def test_kwargs(self, mocker): + data_resp = mocker.Mock() + data_resp.read.return_value = "data" + data_resp.getcode.return_value = 200 + open_url = mocker.patch.object(http, "open_url") + open_url.return_value = data_resp + + http.request("PUT", "example.com/path", a=3, b="f") + + assert "PUT" == open_url.call_args[1]["method"] + assert "example.com/path" == open_url.call_args[1]["url"] + assert 3 == open_url.call_args[1]["a"] + assert "f" == open_url.call_args[1]["b"] + + def test_cert_error_ssl_module_present(self, mocker): + open_url = mocker.patch.object(http, "open_url") + open_url.side_effect = ssl.CertificateError("Invalid") + + with pytest.raises(errors.HttpError): + http.request("GET", "example.com/bad") + + def test_cert_error_ssl_module_absent(self, mocker): + class Dummy(Exception): + pass + + open_url = mocker.patch.object(http, "open_url") + open_url.side_effect = ssl.CertificateError("Invalid") + mocker.patch.object(http, "CertificateError", Dummy) + + with pytest.raises(ssl.CertificateError): + http.request("GET", "example.com/bad") diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_role_utils.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_role_utils.py new file mode 100644 index 000000000..e4f6ff578 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_role_utils.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import role_utils + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoSubjectsDiffer: + def test_different_lengths(self): + assert role_utils._do_subjects_differ( + [{"type": "a", "name": "a"}], + [{"type": "a", "name": "a"}, {"type": "a", "name": "b"}] + ) is True + + def test_different_type_with_same_name(self): + assert role_utils._do_subjects_differ( + [{"type": "a", "name": "same"}], + [{"type": "b", "name": "same"}] + ) is True + + def test_equal_with_different_order_within_type(self): + assert role_utils._do_subjects_differ( + [{"type": "a", "name": "a2"}, {"type": "a", "name": "a1"}], + [{"type": "a", "name": "a1"}, {"type": "a", "name": "a2"}] + ) is False + + def test_equal_with_different_order_multiple_types(self): + assert role_utils._do_subjects_differ( + [ + {"type": "a", "name": "a2"}, + {"type": "b", "name": "b1"}, + {"type": "a", "name": "a1"} + ], + [ + {"type": "a", "name": "a1"}, + {"type": "a", "name": "a2"}, + {"type": "b", "name": "b1"} + ] + ) is False + + def test_different(self): + assert role_utils._do_subjects_differ( + [ + {"type": "a", "name": "a2"}, + {"type": "b", "name": "b3"}, + ], + [ + {"type": "c", "name": "c2"}, + {"type": "a", "name": "s2"}, + ] + ) is True + + +class TestDoRoleBindingsDiffer: + def test_equal_role_binding(self): + current = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "b"}, + {"type": "Group", "name": "g"}, + ] + } + desired = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "b"}, + {"type": "Group", "name": "g"}, + ] + } + assert role_utils.do_role_bindings_differ(current, desired) is False + + def test_equal_role_binding_mixed_users_and_groups(self): + current = { + 'role_ref': 'a', + 'subjects': [ + {"type": "Group", "name": "g1"}, + {"type": "User", "name": "u1"}, + {"type": "Group", "name": "g2"}, + {"type": "User", "name": "u2"}, + ] + } + desired = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "u2"}, + {"type": "User", "name": "u1"}, + {"type": "Group", "name": "g2"}, + {"type": "Group", "name": "g1"}, + ] + } + assert role_utils.do_role_bindings_differ(current, desired) is False + + def test_updated_role_binding_subjects(self): + current = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "b"}, + ] + } + desired = { + 'role_ref': 'a', + 'subjects': [ + {"type": "User", "name": "b"}, + {"type": "Group", "name": "g"}, + ] + } + assert role_utils.do_role_bindings_differ(current, desired) is True + + +class TestRuleSets: + def test_all_keys_none(self): + assert role_utils._rule_set([{}]) == set( + ((frozenset(), frozenset(), frozenset()),) + ) + + def test_rules_multiple(self): + assert role_utils._rule_set([{ + 'verbs': ['list', 'get'], + 'resources': ['entities', 'checks'], + 'resource_names': None + }, { + 'verbs': ['list', 'delete'], + 'resources': ['entities', 'checks'], + 'resource_names': None + }]) == set(( + (frozenset(['delete', 'list']), frozenset(['checks', 'entities']), frozenset()), + (frozenset(['get', 'list']), frozenset(['checks', 'entities']), frozenset()) + )) + + def test_missing_key(self): + assert role_utils._rule_set([{ + 'verbs': ['list', 'get'], + 'resources': ['entities', 'checks'], + }]) == set( + ((frozenset(['get', 'list']), frozenset(['checks', 'entities']), frozenset()),) + ) + + +class TestDoRulesDiffer: + def test_empty_values(self): + assert role_utils._do_rules_differ( + [{'verbs': []}], + [{'verbs': []}] + ) is False + + def test_rules_when_current_values_are_none(self): + assert role_utils._do_rules_differ( + [{'verbs': None}], + [{'verbs': ['get', 'list']}] + ) is True + + def test_rules_when_desired_values_are_none(self): + assert role_utils._do_rules_differ( + [{'verbs': ['get', 'list']}], + [{'verbs': None}] + ) is True + + def test_rules_are_different(self): + assert role_utils._do_rules_differ( + [{'verbs': ['list', 'get']}], + [{'verbs': ['get', 'delete']}] + ) is True + + def test_rules_with_additional_keys_in_current(self): + assert role_utils._do_rules_differ( + [{'verbs': ['list', 'get'], 'resources': ['checks', 'entities']}], + [{'verbs': ['get', 'list']}] + ) is True + + def test_rules_are_the_same(self): + assert role_utils._do_rules_differ( + [{'verbs': ['list', 'get']}], + [{'verbs': ['get', 'list']}] + ) is False + + +class TestDoRolesDiffer: + def test_rules_when_values_in_current_are_none(self): + current = { + 'rules': [{ + 'resource_names': None + }] + } + desired = { + 'rules': [{ + 'resource_names': ['check-cpu'] + }] + } + assert role_utils.do_roles_differ(current, desired) is True + + def test_rules_when_values_in_desired_are_none(self): + current = { + 'rules': [{ + 'resource_names': ['check-cpu'] + }] + } + desired = { + 'rules': [{ + 'resource_names': None + }] + } + assert role_utils.do_roles_differ(current, desired) is True + + def test_different_rules_order(self): + current = { + 'rules': [{ + 'verbs': ['get', 'list'], + 'resources': ['entities', 'checks'] + }, { + 'verbs': ['create', 'delete', 'update'], + 'resources': ['assets', 'hooks'] + }] + } + desired = { + 'rules': [{ + 'verbs': ['delete', 'create', 'update'], + 'resources': ['hooks', 'assets'] + }, { + 'verbs': ['list', 'get'], + 'resources': ['checks', 'entities'] + }] + } + assert role_utils.do_roles_differ(current, desired) is False + + def test_key_missing_in_current(self): + current = { + 'rules': [{ + 'verbs': ['update', 'create'], + 'resources': ['hooks', 'assets'] + }] + } + desired = { + 'rules': [{ + 'verbs': ['create', 'update'], + 'resources': ['hooks', 'assets'], + 'resource_names': ['check-cpu'] + }] + } + assert role_utils.do_roles_differ(current, desired) is True + + def test_key_missing_in_desired(self): + current = { + 'rules': [{ + 'verbs': ['update', 'create'], + 'resources': ['hooks', 'assets'], + 'resource_names': ['check-cpu'] + }] + } + desired = { + 'rules': [{ + 'verbs': ['create', 'update'], + 'resources': ['hooks', 'assets'] + }] + } + assert role_utils.do_roles_differ(current, desired) is True + + def test_role_exists_but_with_additional_rules(self): + current = { + 'rules': [{ + 'verbs': ['get', 'list'], + 'resources': ['entities', 'check'] + }, { + 'verbs': ['create', 'update', 'delete'], + 'resources': ['assets', 'hooks'] + }] + } + desired = { + 'rules': [{ + 'verbs': ['list', 'get'], + 'resources': ['check', 'entities'] + }] + } + assert role_utils.do_roles_differ(current, desired) is True diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_utils.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_utils.py new file mode 100644 index 000000000..f737dce0a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/module_utils/test_utils.py @@ -0,0 +1,491 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, XLAB Steampunk <steampunk@xlab.si> +# +# 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 sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, utils, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSync: + def test_absent_no_current_object(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = utils.sync("absent", client, "/path", {}, False) + + assert changed is False + assert object is None + + def test_absent_no_current_object_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = utils.sync("absent", client, "/path", {}, True) + + assert changed is False + assert object is None + + def test_absent_current_object_present(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + client.delete.return_value = http.Response(204, "") + + changed, object = utils.sync("absent", client, "/path", {}, False) + + assert changed is True + assert object is None + client.delete.assert_called_with("/path") + + def test_absent_current_object_present_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + client.delete.return_value = http.Response(204, "") + + changed, object = utils.sync("absent", client, "/path", {}, True) + + assert changed is True + assert object is None + client.delete.assert_not_called() + + def test_present_no_current_object(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(404, ""), + http.Response(200, '{"new": "data"}'), + ) + client.put.return_value = http.Response(201, "") + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, False, + ) + + assert changed is True + assert {"new": "data"} == object + client.put.assert_called_once_with("/path", {"my": "data"}) + + def test_present_no_current_object_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, True, + ) + + assert changed is True + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_differ(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(200, '{"current": "data"}'), + http.Response(200, '{"new": "data"}'), + ) + client.put.return_value = http.Response(201, "") + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, False, + ) + + assert changed is True + assert {"new": "data"} == object + client.put.assert_called_once_with("/path", {"my": "data"}) + + def test_present_current_object_differ_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"current": "data"}') + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, True, + ) + + assert changed is True + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_does_not_differ(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"my": "data"}') + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, False, + ) + + assert changed is False + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_does_not_differ_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"my": "data"}') + + changed, object = utils.sync( + "present", client, "/path", {"my": "data"}, True, + ) + + assert changed is False + assert {"my": "data"} == object + client.put.assert_not_called() + + +class TestSyncV1: + def test_parameter_passthrough(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = (True, { + "metadata": {"name": "test", "namespace": "space"}, + "spec": {"key": "value"}, + }) + + changed, object = utils.sync_v1("absent", "c", "/path", {}, False) + + assert changed is True + assert { + "metadata": {"name": "test", "namespace": "space"}, + "key": "value", + } + + +class TestDoDiffer: + def test_extra_keys_in_current_do_not_matter(self): + assert utils.do_differ({"a": "b", "c": 3}, {"a": "b"}) is False + + def test_detect_different_values(self): + assert utils.do_differ({"a": "b"}, {"a": "c"}) is True + + def test_detect_missing_keys_in_current(self): + assert utils.do_differ({"a": "b"}, {"c": "d"}) is True + + def test_desired_none_values_are_ignored(self): + assert utils.do_differ({"a": "b"}, {"c": None}) is False + + def test_metadata_ignores_created_by(self): + assert utils.do_differ( + dict(metadata=dict(a=1, created_by=2)), + dict(metadata=dict(a=1)), + ) is False + + def test_metadata_detects_change(self): + assert utils.do_differ( + dict(metadata=dict(a=1)), dict(metadata=dict(a=2)), + ) is True + + def test_metadata_detects_change_in_presence_of_created_by(self): + assert utils.do_differ( + dict(metadata=dict(a=1, created_by=2)), + dict(metadata=dict(a=2)), + ) is True + + def test_ignore_keys_do_not_affect_the_outcome(self): + assert utils.do_differ(dict(a=1), dict(a=2), "a") is False + + def test_ignore_keys_do_not_mask_other_differences(self): + assert utils.do_differ(dict(a=1, b=1), dict(a=2, b=2), "a") is True + + +class TestDoDifferV1: + def test_extra_keys_in_current_do_not_matter(self): + assert utils.do_differ_v1( + {"spec": {"a": "b", "c": 3}}, {"spec": {"a": "b"}}, + ) is False + + def test_detect_different_values(self): + assert utils.do_differ_v1( + {"spec": {"a": "b"}}, {"spec": {"a": "c"}}, + ) is True + + def test_detect_missing_keys_in_current(self): + assert utils.do_differ_v1( + {"spec": {"a": "b"}}, {"spec": {"c": "d"}}, + ) is True + + def test_desired_none_values_are_ignored(self): + assert utils.do_differ_v1( + {"spec": {"a": "b"}}, {"spec": {"c": None}}, + ) is False + + def test_metadata_ignores_created_by(self): + assert utils.do_differ_v1( + {"metadata": {"a": 1, "created_by": 2}}, + {"metadata": {"a": 1}}, + ) is False + + def test_metadata_detects_change(self): + assert utils.do_differ_v1( + {"metadata": {"a": 1}}, {"metadata": {"a": 2}}, + ) is True + + def test_metadata_detects_change_in_presence_of_created_by(self): + assert utils.do_differ_v1( + {"metadata": {"a": 1, "created_by": 2}}, + {"metadata": {"a": 2}}, + ) is True + + def test_ignore_keys_do_not_affect_the_outcome(self): + assert utils.do_differ_v1( + {"spec": {"a": 1}}, {"spec": {"a": 2}}, "a", + ) is False + + def test_ignore_keys_do_not_mask_other_differences(self): + assert utils.do_differ_v1( + {"spec": {"a": 1, "b": 1}}, {"spec": {"a": 2, "b": 2}}, "a", + ) is True + + +class TestGet: + @pytest.mark.parametrize( + "status", [100, 201, 202, 203, 204, 400, 401, 403, 500, 501], + ) + def test_abort_on_invalid_status(self, mocker, status): + client = mocker.Mock() + client.get.return_value = http.Response(status, "") + + with pytest.raises(errors.SyncError, match=str(status)): + utils.get(client, "/get") + client.get.assert_called_once_with("/get") + + def test_abort_on_invalid_json(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, "") + + with pytest.raises(errors.SyncError, match="JSON"): + utils.get(client, "/get") + client.get.assert_called_once_with("/get") + + def test_ignore_invalid_json_on_404(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + object = utils.get(client, "/get") + + assert object is None + client.get.assert_called_once_with("/get") + + def test_valid_json(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"get": "data"}') + + object = utils.get(client, "/get") + + assert {"get": "data"} == object + client.get.assert_called_once_with("/get") + + +class TestDelete: + @pytest.mark.parametrize( + "status", [100, 200, 201, 202, 203, 400, 401, 403, 500, 501], + ) + def test_abort_on_invalid_status(self, mocker, status): + client = mocker.Mock() + client.delete.return_value = http.Response(status, "") + + with pytest.raises(errors.SyncError, match=str(status)): + utils.delete(client, "/delete") + client.delete.assert_called_once_with("/delete") + + def test_valid_delete(self, mocker): + client = mocker.Mock() + client.delete.return_value = http.Response(204, "{}") + + object = utils.delete(client, "/delete") + + assert object is None + client.delete.assert_called_once_with("/delete") + + +class TestPut: + @pytest.mark.parametrize( + "status", [100, 202, 203, 204, 400, 401, 403, 500, 501], + ) + def test_abort_on_invalid_status(self, mocker, status): + client = mocker.Mock() + client.put.return_value = http.Response(status, "") + + with pytest.raises(errors.SyncError, match=str(status)): + utils.put(client, "/put", {"payload": "data"}) + client.put.assert_called_once_with("/put", {"payload": "data"}) + + @pytest.mark.parametrize("status", [200, 201]) + def test_valid_put(self, mocker, status): + client = mocker.Mock() + client.put.return_value = http.Response(status, '{"put": "data"}') + + object = utils.put(client, "/put", {"payload": "data"}) + + assert object is None + client.put.assert_called_once_with("/put", {"payload": "data"}) + + +class TestDictToSingleItemDicts: + def test_conversion(self): + result = utils.dict_to_single_item_dicts({"a": 0, 1: "b"}) + + assert 2 == len(result) + for item in ({"a": 0}, {1: "b"}): + assert item in result + + +class TestSingleItemDictsToDict: + def test_conversion(self): + assert dict(a=3, b=4, c=5) == utils.single_item_dicts_to_dict( + [dict(a=3), dict(b=4), dict(c=5)] + ) + + +class TestDictToKeyValueString: + def test_conversion(self): + result = utils.dict_to_key_value_strings({"a": 0, 1: "b"}) + + assert set(("a=0", "1=b")) == set(result) + + +class TestBuildUrlPath: + @pytest.mark.parametrize("parts,expectation", [ + ((), "/"), + ((None, None), "/"), + ((None, "a", "b", None, None, "c"), "/a/b/c"), + (("get/rid of+stuff",), "/get%2Frid%20of%2Bstuff"), + (("/", " ", "a"), "/%2F/%20/a"), + ]) + def test_build_url_path_no_namespace(self, parts, expectation): + path = "/api/enterprise/store/v1" + expectation + assert path == utils.build_url_path( + "enterprise/store", "v1", None, *parts + ) + + @pytest.mark.parametrize("parts,expectation", [ + ((), "/"), + ((None, None), "/"), + ((None, "a", "b", None, None, "c"), "/a/b/c"), + (("get/rid of+stuff",), "/get%2Frid%20of%2Bstuff"), + (("/", " ", "a"), "/%2F/%20/a"), + ]) + def test_build_url_path_with_namespace(self, parts, expectation): + path = "/api/core/v2/namespaces/default" + expectation + assert path == utils.build_url_path( + "core", "v2", "default", *parts + ) + + +class TestBuildCoreV2Path: + def test_build_path_no_namespace(self): + assert utils.build_core_v2_path(None, "a").startswith( + "/api/core/v2/", + ) + + def test_build_url_with_namespace(self): + assert utils.build_core_v2_path("default", "a").startswith( + "/api/core/v2/namespaces/default/", + ) + + +class TestPrepareResultList: + @pytest.mark.parametrize("input,output", [ + (None, []), # this is mosti likely result of a 404 status + ("a", ["a"]), + ([], []), + ([1, 2, 3], [1, 2, 3]), + ([None], [None]), # we leave lists intact, even if they contain None + ]) + def test_list_construction(self, input, output): + assert output == utils.prepare_result_list(input) + + +class TestConvertV1ToV2Response: + def test_none_passes_through(self): + assert utils.convert_v1_to_v2_response(None) is None + + def test_spec_only_if_metadata_is_missing(self): + assert utils.convert_v1_to_v2_response(dict( + spec=dict(a=1, b=2), + )) == dict(a=1, b=2) + + def test_add_metadata_from_toplevel(self): + assert utils.convert_v1_to_v2_response(dict( + metadata=dict(name="sample"), + spec=dict(a=1, b=2), + )) == dict(metadata=dict(name="sample"), a=1, b=2) + + +class TestDoSecretsDiffer: + @pytest.mark.parametrize("current,desired", [ + ( # All empty + [], [], + ), + ( # All is equal + [dict(name="a", secret="1"), dict(name="b", secret="2")], + [dict(name="a", secret="1"), dict(name="b", secret="2")], + ), + ( # Different order + [dict(name="a", secret="1"), dict(name="b", secret="2")], + [dict(name="b", secret="2"), dict(name="a", secret="1")], + ), + ]) + def test_no_difference(self, current, desired): + assert utils.do_secrets_differ( + dict(secrets=current), dict(secrets=desired), + ) is False + + @pytest.mark.parametrize("current,desired", [ + ( # Different source for variable b + [dict(name="b", secret="2")], [dict(name="b", secret="3")], + ), + ( # Different name + [dict(name="a", secret="1")], [dict(name="b", secret="1")], + ), + ( # Different number of secrets + [dict(name="a", secret="1"), dict(name="b", secret="2")], + [dict(name="a", secret="1")], + ), + ]) + def test_difference(self, current, desired): + assert utils.do_secrets_differ( + dict(secrets=current), dict(secrets=desired), + ) is True + + @pytest.mark.parametrize("secrets,diff", [ + # Missing secrets and empty list are the same + ([], False), + # None secrets are treated as empy list of secrets + (None, False), + # If anything is set, we have difference + ([dict(name="n", secret="s")], True), + ]) + def test_missing_secrets(self, secrets, diff): + assert utils.do_secrets_differ(dict(), dict(secrets=secrets)) is diff + assert utils.do_secrets_differ(dict(secrets=secrets), dict()) is diff + + +class TestDeprecate: + def test_ansible_lt_2_9_10(self, mocker): + module = mocker.MagicMock() + module.deprecate.side_effect = ( + TypeError("Simulating Ansible 2.9.9 and older"), + None, # Success, since no exception is raised + ) + + utils.deprecate(module, "Test msg", "3.2.1") + + assert module.deprecate.call_count == 2 + assert module.deprecate.called_once_with("Test msg", version="3.2.1") + + def test_ansible_ge_2_9_10(self, mocker): + module = mocker.MagicMock() + + utils.deprecate(module, "Test msg", "3.2.1") + + assert module.deprecate.called_once_with( + "Test msg", version="3.2.1", collection_name="sensu.sensu_go", + ) diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/common/utils.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/common/utils.py new file mode 100644 index 000000000..2287dbc9e --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/common/utils.py @@ -0,0 +1,53 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + +from mock import patch + + +def set_module_args(**args): + if '_ansible_remote_tmp' not in args: + args['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in args: + args['_ansible_keep_remote_files'] = False + + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + pass + + +class AnsibleFailJson(Exception): + pass + + +def exit_json(*args, **kwargs): + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class ModuleTestCase: + def setup_method(self): + self.mock_module = patch.multiple( + basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json, + ) + self.mock_module.start() + + def teardown_method(self): + self.mock_module.stop() + + +def generate_name(test_case): + return test_case['name'] diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ad_auth_provider.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ad_auth_provider.py new file mode 100644 index 000000000..a7204bb5a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ad_auth_provider.py @@ -0,0 +1,332 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, + utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import ad_auth_provider + +from .common.utils import ( + AnsibleExitJson, + AnsibleFailJson, + ModuleTestCase, + set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + def test_no_changes(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict(name="activedirectory"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="activedirectory", + created_by="me", + ), + ) + + assert ad_auth_provider.do_differ(current, desired) is False + + def test_changes_are_detected(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=636, + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict(name="activedirectory"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="activedirectory", + created_by="me", + ), + ) + assert ad_auth_provider.do_differ(current, desired) is True + + def test_changes_are_detected_diff_servers_len(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ), + dict( + host="127.0.0.2", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ), + ], + ), + metadata=dict(name="activedirectory"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="activedirectory", + created_by="me", + ), + ) + assert ad_auth_provider.do_differ(current, desired) is True + + def test_changes_are_other_params(self): + desired = dict( + spec=dict( + servers=[], + groups_prefix="ad", + username_prefix="ad", + ), + metadata=dict(name="activedirectory"), + ) + current = dict( + spec=dict( + servers=[], + ), + metadata=dict( + name="activedirectory", + created_by="me", + ), + ) + assert ad_auth_provider.do_differ(current, desired) is True + + +class TestADAutProvider(ModuleTestCase): + def test_minimal_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="activedirectory", + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ) + + with pytest.raises(AnsibleExitJson): + ad_auth_provider.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[ + 0 + ] + + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/activedirectory" + assert payload == dict( + type="ad", + api_version="authentication/v2", + metadata=dict(name="activedirectory"), + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=None, + insecure=False, + security="tls", + trusted_ca_file=None, + client_cert_file=None, + client_key_file=None, + default_upn_domain=None, + include_nested_groups=None, + binding=None, + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="group", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="sAMAccountName", + name_attribute="displayName", + object_class="person", + ), + ) + ] + ), + ) + + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="activedirectory", + servers=[ + dict( + host="127.0.0.1", + port=636, + insecure=False, + security="tls", + trusted_ca_file="/path/to/trusted-certificate-authorities.pem", + client_cert_file="/path/to/ssl/cert.pem", + client_key_file="/path/to/ssl/key.pem", + default_upn_domain="example.org", + include_nested_groups=True, + binding=dict( + user_dn="cn=binder,dc=acme,dc=org", + password="YOUR_PASSWORD", + ), + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="group", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="sAMAccountName", + name_attribute="displayName", + object_class="person", + ), + ) + ], + groups_prefix="ad", + username_prefix="ad", + ) + + with pytest.raises(AnsibleExitJson): + ad_auth_provider.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[ + 0 + ] + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/activedirectory" + assert payload == dict( + type="ad", + api_version="authentication/v2", + metadata=dict(name="activedirectory"), + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=636, + insecure=False, + security="tls", + trusted_ca_file="/path/to/trusted-certificate-authorities.pem", + client_cert_file="/path/to/ssl/cert.pem", + client_key_file="/path/to/ssl/key.pem", + default_upn_domain="example.org", + include_nested_groups=True, + binding=dict( + user_dn="cn=binder,dc=acme,dc=org", + password="YOUR_PASSWORD", + ), + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="group", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="sAMAccountName", + name_attribute="displayName", + object_class="person", + ), + ) + ], + groups_prefix="ad", + username_prefix="ad", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + ad_auth_provider.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset.py new file mode 100644 index 000000000..8e9298b2c --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset.py @@ -0,0 +1,251 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import asset + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + def test_equal_assets_with_none_values(self): + assert asset.do_differ( + { + "name": "asset", + "builds": [ + { + "sha512": "a", + "url": "a", + "filters": None + }, + ], + }, + { + "name": "asset", + "builds": [ + { + "sha512": "a", + "url": "a", + "headers": None, + }, + ], + }, + ) is False + + def test_equal_assets_with_different_build_content(self): + assert asset.do_differ( + { + "name": "asset", + "builds": [ + { + "url": "http://abc.com", + "sha512": "abc", + "headers": { + "foo": "bar", + "bar": "foo", + } + }, + { + "url": "http://def.com", + "sha512": "def", + "filters": ["d == d", "e == e"], + }, + + ] + }, + { + "name": "asset", + "builds": [ + { + "url": "http://def.com", + "sha512": "def", + "filters": ["e == e", "d == d"], + }, + { + "url": "http://abc.com", + "sha512": "abc", + "headers": { + "bar": "foo", + "foo": "bar" + } + }, + ] + }, + ) is False + + def test_updated_asset(self): + assert asset.do_differ( + { + "name": "asset", + "builds": [ + { + "url": "http://abc.com", + "sha512": "abc", + } + ], + "annotations": { + "foo": "bar", + } + }, + { + "name": "asset", + "builds": [ + { + "url": "http://def.com", + "sha512": "abc", + }, + { + "url": "http://def.com", + "sha512": "abc", + "filters": ["abc == def"], + } + ], + }, + ) is True + + def test_different_assets_with_same_builds(self): + assert asset.do_differ( + { + "name": "a", + "builds": [ + { + "url": "http://abc.com", + "sha512": "abc", + } + ], + "annotations": { + "foo": "bar", + } + }, + { + "name": "b", + "builds": [ + { + "url": "http://abc.com", + "sha512": "abc", + } + ], + "annotations": { + "bar": "foo" + } + }, + ) is True + + +class TestAsset(ModuleTestCase): + def test_minimal_asset_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_asset", + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + ), + ] + ) + + with pytest.raises(AnsibleExitJson): + asset.main() + + state, _client, path, payload, check_mode, _do_differ = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/default/assets/test_asset" + assert payload == dict( + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + ), + ], + metadata=dict( + name="test_asset", + namespace="default", + ), + ) + assert check_mode is False + + def test_all_asset_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_asset", + namespace="my", + state="present", + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + filters=["a", "b", "c"], + headers={"header": "h"}, + ), + ], + labels={"region": "us-west-1"}, + annotations={"playbook": 12345}, + ) + + with pytest.raises(AnsibleExitJson): + asset.main() + + state, _client, path, payload, check_mode, _do_differ = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/my/assets/test_asset" + assert payload == dict( + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + filters=["a", "b", "c"], + headers={"header": "h"}, + ) + ], + metadata=dict( + name="test_asset", + namespace="my", + labels={"region": "us-west-1"}, + annotations={"playbook": "12345"}, + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name="test_asset", + builds=[ + dict( + url="http://example.com/asset.tar.gz", + sha512="sha512String", + ) + ] + + ) + + with pytest.raises(AnsibleFailJson): + asset.main() + + def test_failure_empty_builds(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name="test_asset", + builds=[], + ) + + with pytest.raises(AnsibleFailJson): + asset.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset_info.py new file mode 100644 index 000000000..aab09b571 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_asset_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import asset_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestAssetInfo(ModuleTestCase): + def test_get_all_assets(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + asset_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/assets" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_asset(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-asset") + + with pytest.raises(AnsibleExitJson) as context: + asset_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/assets/sample-asset" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_asset(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-asset") + + with pytest.raises(AnsibleExitJson) as context: + asset_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-asset") + + with pytest.raises(AnsibleFailJson): + asset_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_auth_provider_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_auth_provider_info.py new file mode 100644 index 000000000..bd19bceb1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_auth_provider_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import auth_provider_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestAuthProviderInfo(ModuleTestCase): + def test_get_all_auth_providers(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [dict(spec=dict(a=1)), dict(spec=dict(b=2))] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + auth_provider_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/authentication/v2/authproviders" + assert context.value.args[0]["objects"] == [dict(a=1), dict(b=2)] + + def test_get_single_auth_provider(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = dict(spec=dict(a=1)) + set_module_args(name="sample-auth-provider") + + with pytest.raises(AnsibleExitJson) as context: + auth_provider_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/authentication/v2/authproviders/sample-auth-provider" + assert context.value.args[0]["objects"] == [dict(a=1)] + + def test_missing_single_auth_provider(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-auth-provider") + + with pytest.raises(AnsibleExitJson) as context: + auth_provider_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-auth-provider") + + with pytest.raises(AnsibleFailJson): + auth_provider_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_bonsai_asset.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_bonsai_asset.py new file mode 100644 index 000000000..e935a7a9f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_bonsai_asset.py @@ -0,0 +1,47 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import bonsai, errors +from ansible_collections.sensu.sensu_go.plugins.modules import bonsai_asset + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestBonsaiAsset(ModuleTestCase): + def test_success(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.return_value = dict(sample="value") + + set_module_args(name="name", version="version") + + with pytest.raises(AnsibleExitJson): + bonsai_asset.main() + + def test_bonsai_failure(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.side_effect = errors.BonsaiError("Bonsai bad") + + set_module_args(name="name", version="version") + + with pytest.raises(AnsibleFailJson): + bonsai_asset.main() + + def test_validation_failure(self, mocker): + bonsai_params = mocker.patch.object(bonsai, "get_asset_parameters") + bonsai_params.return_value = dict(sample="value") + + set_module_args(version="version") + + with pytest.raises(AnsibleFailJson): + bonsai_asset.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check.py new file mode 100644 index 000000000..8ea6ecb94 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check.py @@ -0,0 +1,327 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import check + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoSetsDiffer: + @pytest.mark.parametrize("current,desired,diff", [ + ([1, 2, 3], [1, 2, 3], False), + ([1, 2, 3], [3, 2, 1], False), + ([1, 2], [1, 2, 3], True), + ([1, 3], [2, 4], True), + ]) + def test_comparison(self, current, desired, diff): + c = dict(k=current) + d = dict(k=desired) + + assert check.do_sets_differ(c, d, "k") is diff + + def test_missing_keys_are_treated_as_empty_sets(self): + current = dict(a=[]) + desired = dict() + + assert check.do_sets_differ(current, desired, "a") is False + assert check.do_sets_differ(desired, current, "a") is False + + def test_nulls_are_treated_as_empty_sets(self): + current = dict(a=None) + desired = dict(a=[]) + + assert check.do_sets_differ(current, desired, "a") is False + assert check.do_sets_differ(desired, current, "a") is False + + +class TestDoProxyRequestsDiffer: + def test_missing_proxy_requests_in_desired_is_ignored(self): + current = dict(proxy_requests=dict(entity_attributes=["a", "b"])) + desired = dict() + + assert check.do_proxy_requests_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired,diff", [ + (["a", "b"], ["a", "b"], False), + (["a", "b"], ["b", "a"], False), + (None, [], False), + (["a", "b"], ["c", "a"], True), + (["a", "b"], ["a", "b", "c"], True), + ]) + def test_treat_entity_attributes_as_a_set(self, current, desired, diff): + c = dict(proxy_requests=dict(entity_attributes=current)) + d = dict(proxy_requests=dict(entity_attributes=desired)) + + assert check.do_proxy_requests_differ(c, d) is diff + + def test_ignore_missing_entity_attributes_in_desired(self): + current = dict(proxy_requests=dict(entity_attributes=["a", "b"])) + desired = dict(proxy_requests=dict()) + + assert check.do_proxy_requests_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired,diff", [ + (dict(splay=False), dict(splay=False), False), + (dict(splay=False), dict(), False), + (dict(splay=False), dict(splay=True), True), + (dict(), dict(splay=True), True), + ]) + def test_other_stuff_is_compared_as_usual(self, current, desired, diff): + c = dict(proxy_requests=current) + d = dict(proxy_requests=desired) + + assert check.do_proxy_requests_differ(c, d) is diff + + +class TestDoCheckHooksDiffer: + def test_missing_check_hooks_in_desired_is_ignored(self): + current = dict(check_hooks=[dict(warning=["a"])]) + desired = dict() + + assert check.do_check_hooks_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired,diff", [ + (["a", "b"], ["a", "b"], False), + (["a", "b"], ["b", "a"], False), + (["a", "b"], ["c", "a"], True), + (["a", "b"], ["a", "b", "c"], True), + ]) + def test_treat_hooks_as_a_set(self, current, desired, diff): + c = dict(check_hooks=[dict(warning=current)]) + d = dict(check_hooks=[dict(warning=desired)]) + + assert check.do_check_hooks_differ(c, d) is diff + + +class TestDoDiffer: + def test_no_difference(self): + assert not check.do_differ( + dict( + command="sleep", + subscriptions=["sub1", "sub2"], + handlers=["ha1", "ha2", "ha3"], + interval=123, + cron="* * * 3 2", + publish=False, + timeout=30, + ttl=60, + stdin=True, + low_flap_threshold=2, + high_flap_threshold=10, + runtime_assets=["asset1", "asset2"], + check_hooks=[ + dict(warning=["hook0-1", "hook0-2"]), + dict(critical=["hook1-1", "hook1-2"]), + ], + proxy_entity_name="name", + proxy_requests=dict( + entity_attributes=["a1", "a2", "a3"], + splay=True, + splay_coverage=10, + ), + output_metric_format="influxdb_line", + output_metric_handlers=["mhandler1", "mhandler2"], + round_robin=False, + env_vars=["k1=v1", "k2=v2"], + ), + dict( + command="sleep", + subscriptions=["sub2", "sub1"], + handlers=["ha3", "ha1", "ha2"], + interval=123, + cron="* * * 3 2", + publish=False, + timeout=30, + ttl=60, + stdin=True, + low_flap_threshold=2, + high_flap_threshold=10, + runtime_assets=["asset2", "asset1"], + check_hooks=[ + dict(critical=["hook1-2", "hook1-1"]), + dict(warning=["hook0-2", "hook0-1"]), + ], + proxy_entity_name="name", + proxy_requests=dict( + splay=True, + entity_attributes=["a3", "a2", "a1"], + splay_coverage=10, + ), + output_metric_format="influxdb_line", + output_metric_handlers=["mhandler2", "mhandler1"], + round_robin=False, + env_vars=["k2=v2", "k1=v1"], + ) + ) + + @pytest.mark.parametrize("current,desired", [ + ( # No diff in params, no secrets + dict(name="demo"), + dict(name="demo"), + ), + ( # No diff in params, no diff in secrets + dict(name="demo", secrets=[ + dict(name="n1", secret="s1"), dict(name="n2", secret="s2"), + ]), + dict(name="demo", secrets=[ + dict(name="n2", secret="s2"), dict(name="n1", secret="s1"), + ]), + ), + ]) + def test_no_difference_secrets(self, current, desired): + assert check.do_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired", [ + ( # Diff in params, no diff in secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="a", secret="1")]), + ), + ( # No diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="demo", secrets=[dict(name="b", secret="2")]), + ), + ( # Diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="b", secret="2")]), + ), + ]) + def test_difference_secrets(self, current, desired): + assert check.do_differ(current, desired) is True + + +class TestSensuGoCheck(ModuleTestCase): + def test_minimal_check_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_check", + command='echo "test"', + subscriptions=['switches'], + interval=60 + ) + + with pytest.raises(AnsibleExitJson): + check.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/default/checks/test_check" + assert payload == dict( + command='echo "test"', + subscriptions=['switches'], + interval=60, + metadata=dict( + name="test_check", + namespace="default", + ), + ) + assert check_mode is False + + def test_all_check_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name='test_check', + namespace='my', + state='absent', + command='/bin/true', + subscriptions=['checks', 'also_checks'], + handlers=['default', 'not_default'], + interval=30, + publish=True, + timeout=30, + ttl=100, + stdin=False, + low_flap_threshold=20, + high_flap_threshold=60, + proxy_entity_name='switch-dc-01', + proxy_requests=dict( + entity_attributes=['entity.entity_class == "proxy"'], + splay=True, + splay_coverage=90 + ), + output_metric_format='nagios_perfdata', + output_metric_handlers=['influx-db'], + round_robin=True, + env_vars=dict(foo='bar'), + runtime_assets='awesomeness', + secrets=[dict(name="a", secret="b")], + ) + + with pytest.raises(AnsibleExitJson): + check.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == "absent" + assert path == "/api/core/v2/namespaces/my/checks/test_check" + assert payload == dict( + command='/bin/true', + subscriptions=['checks', 'also_checks'], + interval=30, + timeout=30, + publish=True, + handlers=['default', 'not_default'], + env_vars=['foo=bar'], + output_metric_handlers=['influx-db'], + ttl=100, + output_metric_format='nagios_perfdata', + proxy_entity_name='switch-dc-01', + proxy_requests=dict(entity_attributes=['entity.entity_class == "proxy"'], + splay=True, + splay_coverage=90), + high_flap_threshold=60, + low_flap_threshold=20, + round_robin=True, + stdin=False, + runtime_assets=['awesomeness'], + metadata=dict( + name="test_check", + namespace="my", + ), + secrets=[dict(name="a", secret="b")], + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_check', + command='/bin/true', + subscriptions=['checks', 'also_checks'], + handlers=['default', 'not_default'], + interval=30, + publish=True, + timeout=30, + ttl=100, + stdin=False, + low_flap_threshold=20, + high_flap_threshold=60, + proxy_entity_name='switch-dc-01', + proxy_requests=dict( + entity_attributes=['entity.entity_class == "proxy"'], + splay=True, + splay_coverage=90 + ), + output_metric_format='nagios_perfdata', + output_metric_handlers=['influx-db'], + round_robin=True, + env_vars=dict(foo='bar'), + runtime_assets='awesomeness' + ) + + with pytest.raises(AnsibleFailJson): + check.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check_info.py new file mode 100644 index 000000000..53ed5b7c6 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_check_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import check_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSensuGoCheckInfo(ModuleTestCase): + def test_get_all_checks(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + check_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/checks" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_check(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-check") + + with pytest.raises(AnsibleExitJson) as context: + check_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/checks/sample-check" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_check(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-check") + + with pytest.raises(AnsibleExitJson) as context: + check_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-check") + + with pytest.raises(AnsibleFailJson): + check_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster.py new file mode 100644 index 000000000..289933644 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster.py @@ -0,0 +1,83 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestCluster(ModuleTestCase): + @pytest.mark.parametrize("params", [ + {"name": "demo", "state": "absent"}, + {"name": "demo", "api_urls": "url"}, + ]) + def test_minimal_parameters(self, mocker, params): + mocker.patch.object(utils, "sync_v1").return_value = True, {} + set_module_args(**params) + + with pytest.raises(AnsibleExitJson): + cluster.main() + + def test_all_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.return_value = True, {} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + state="present", + name="demo", + api_urls=["a", "b"], + ) + + with pytest.raises(AnsibleExitJson): + cluster.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/enterprise/federation/v1/clusters/demo" + assert payload == dict( + type="Cluster", + api_version="federation/v1", + metadata=dict(name="demo"), + spec=dict(api_urls=["a", "b"]), + ) + assert check_mode is False + + @pytest.mark.parametrize("skip", ["api_urls"]) + def test_missing_required_param_present(self, mocker, skip): + sync_mock = mocker.patch.object(utils, "sync_v1") + all_args = dict(name="demo", api_urls="url") + set_module_args(**dict((k, v) for k, v in all_args.items() if k != skip)) + + with pytest.raises(AnsibleFailJson): + cluster.main() + + sync_mock.assert_not_called() + + def test_failure(self, mocker): + mocker.patch.object(utils, "sync_v1").side_effect = ( + errors.Error("Bad error") + ) + set_module_args(name="demo", state="absent") + + with pytest.raises(AnsibleFailJson): + cluster.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_info.py new file mode 100644 index 000000000..45158dcd3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_info.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterInfo(ModuleTestCase): + def test_all_parameters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k1": "v1"}} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + name="demo", + ) + + with pytest.raises(AnsibleExitJson): + cluster_info.main() + + def test_get_all_clusters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [ + {"spec": {"k1": "v1"}}, {"spec": {"k2": "v2"}}, + ] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + cluster_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/federation/v1/clusters" + assert context.value.args[0]["objects"] == [ + {"k1": "v1"}, {"k2": "v2"}, + ] + + def test_get_single_cluster(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k3": "v3"}} + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + cluster_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/federation/v1/clusters/demo" + assert context.value.args[0]["objects"] == [{"k3": "v3"}] + + def test_missing_single_cluster(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + cluster_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="demo") + + with pytest.raises(AnsibleFailJson): + cluster_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role.py new file mode 100644 index 000000000..48e939a03 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role.py @@ -0,0 +1,128 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_role + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterRole(ModuleTestCase): + def test_minimal_cluster_role_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role', + rules=[ + dict( + verbs=[], + resources=[] + ), + ] + ) + + with pytest.raises(AnsibleExitJson): + cluster_role.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterroles/test_cluster_role' + assert payload == dict( + rules=[ + dict( + verbs=[], + resources=[], + resource_names=None, + ) + ], + metadata=dict( + name='test_cluster_role', + ), + ) + assert check_mode is False + + def test_all_cluster_role_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role', + state='present', + rules=[ + dict( + verbs=['get', 'list', 'create'], + resources=['assets', 'entities'], + ), + dict( + verbs=['list'], + resources=['check'], + resource_names=['my-check'], + ) + ], + ) + + with pytest.raises(AnsibleExitJson): + cluster_role.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterroles/test_cluster_role' + assert payload == dict( + metadata=dict( + name='test_cluster_role', + ), + rules=[ + dict( + verbs=['get', 'list', 'create'], + resources=['assets', 'entities'], + resource_names=None, + ), + dict( + verbs=['list'], + resources=['check'], + resource_names=['my-check'], + ) + ], + ) + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_cluster_role', + rules=[ + dict( + verbs=[], + resources=[], + ) + ], + ) + with pytest.raises(AnsibleFailJson): + cluster_role.main() + + def test_failure_invalid_verb(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_cluster_role', + rules=[ + dict( + verbs=['list', 'invalid'], + resources=[], + ), + ] + ) + + with pytest.raises(AnsibleFailJson): + cluster_role.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding.py new file mode 100644 index 000000000..b18c2d226 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding.py @@ -0,0 +1,152 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_role_binding + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterRoleBinding(ModuleTestCase): + def test_minimal_cluster_role_binding_parameters_users(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + users=['test_user'], + ) + + with pytest.raises(AnsibleExitJson): + cluster_role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterrolebindings/test_cluster_role_binding' + assert payload == dict( + role_ref=dict( + name='test_cluster_role', + type='ClusterRole', + ), + subjects=[ + dict( + name='test_user', + type='User', + ), + ], + metadata=dict( + name='test_cluster_role_binding', + ), + ) + assert check_mode is False + + def test_minimal_cluster_role_binding_parameters_groups(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + groups=['test_group'], + ) + + with pytest.raises(AnsibleExitJson): + cluster_role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterrolebindings/test_cluster_role_binding' + assert payload == dict( + role_ref=dict( + name='test_cluster_role', + type='ClusterRole', + ), + subjects=[ + dict( + name='test_group', + type='Group', + ), + ], + metadata=dict( + name='test_cluster_role_binding', + ), + ) + assert check_mode is False + + def test_all_cluster_role_binding_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + users=['user_1', 'user_2'], + groups=['group_1', 'group_2'], + ) + + with pytest.raises(AnsibleExitJson): + cluster_role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/clusterrolebindings/test_cluster_role_binding' + assert payload == dict( + metadata=dict( + name='test_cluster_role_binding', + ), + role_ref=dict( + name='test_cluster_role', + type='ClusterRole', + ), + subjects=[ + dict( + name='group_1', + type='Group', + ), + dict( + name='group_2', + type='Group', + ), + dict( + name='user_1', + type='User', + ), + dict( + name='user_2', + type='User', + ), + ] + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + users=['test_user'], + ) + with pytest.raises(AnsibleFailJson): + cluster_role_binding.main() + + def test_failure_missing_groups_or_users(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_cluster_role_binding', + cluster_role='test_cluster_role', + ) + + with pytest.raises(AnsibleFailJson): + cluster_role_binding.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding_info.py new file mode 100644 index 000000000..34e58451f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_binding_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_role_binding_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterRoleBindingInfo(ModuleTestCase): + def test_get_all_cluster_role_bindings(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_binding_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/clusterrolebindings" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_cluster_role_binding(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 1 + set_module_args(name="test-cluster-role-binding") + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_binding_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/clusterrolebindings/test-cluster-role-binding" + assert context.value.args[0]["objects"] == [1] + + def test_missing_single_cluster_role_binding(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-cluster-role-binding") + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_binding_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-cluster-role-binding") + + with pytest.raises(AnsibleFailJson): + cluster_role_binding_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_info.py new file mode 100644 index 000000000..11eadb6a6 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_cluster_role_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import cluster_role_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestClusterRoleInfo(ModuleTestCase): + def test_get_all_cluster_roles(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/clusterroles" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_cluster_role(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 1 + set_module_args(name="test-cluster-role") + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/clusterroles/test-cluster-role" + assert context.value.args[0]["objects"] == [1] + + def test_missing_single_cluster_role(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-cluster-role") + + with pytest.raises(AnsibleExitJson) as context: + cluster_role_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-cluster-role") + + with pytest.raises(AnsibleFailJson): + cluster_role_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore.py new file mode 100644 index 000000000..665b29ab8 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore.py @@ -0,0 +1,269 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, +) +from ansible_collections.sensu.sensu_go.plugins.modules import datastore + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSync(ModuleTestCase): + def test_absent_no_current_object(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = datastore.sync( + "absent", client, "/list", "/resource", {}, False, + ) + + assert changed is False + assert object is None + + def test_absent_no_current_object_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, "") + + changed, object = datastore.sync( + "absent", client, "/list", "/resource", {}, True, + ) + + assert changed is False + assert object is None + + def test_absent_current_object_present(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + client.delete.return_value = http.Response(204, "") + + changed, object = datastore.sync( + "absent", client, "/list", "/resource", {}, False, + ) + + assert changed is True + assert object is None + client.delete.assert_called_with("/resource") + + def test_absent_current_object_present_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + client.delete.return_value = http.Response(204, "") + + changed, object = datastore.sync( + "absent", client, "/list", "/resource", {}, True, + ) + + assert changed is True + assert object is None + client.delete.assert_not_called() + + def test_present_current_object_differ(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(200, '{"spec": {"current": "data"}}'), + http.Response(200, '{"spec": {"new": "data"}}'), + ) + client.put.return_value = http.Response(201, "") + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + False, + ) + + assert changed is True + assert {"new": "data"} == object + client.put.assert_called_once_with( + "/resource", {"spec": {"my": "data"}}, + ) + + def test_present_current_object_differ_check(self, mocker): + client = mocker.Mock() + client.get.return_value = ( + http.Response(200, '{"spec": {"current": "data"}}') + ) + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + True, + ) + + assert changed is True + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_does_not_differ(self, mocker): + client = mocker.Mock() + client.get.return_value = ( + http.Response(200, '{"spec": {"my": "data"}}') + ) + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + False, + ) + + assert changed is False + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_current_object_does_not_differ_check(self, mocker): + client = mocker.Mock() + client.get.return_value = ( + http.Response(200, '{"spec": {"my": "data"}}') + ) + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + True, + ) + + assert changed is False + assert {"my": "data"} == object + client.put.assert_not_called() + + def test_present_no_current_object_empty_backend(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(404, ""), + http.Response(200, "[]"), + http.Response(200, '{"spec": {"new": "data"}}'), + ) + client.put.return_value = http.Response(201, "") + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + False, + ) + + assert changed is True + assert {"new": "data"} == object + client.put.assert_called_once_with( + "/resource", {"spec": {"my": "data"}}, + ) + + def test_present_no_current_object_empty_backend_check(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(404, ""), + http.Response(200, "[]"), + ) + + changed, object = datastore.sync( + "present", client, "/list", "/resource", {"spec": {"my": "data"}}, + True, + ) + + assert changed is True + assert {"my": "data"} == object + client.put.assert_not_called() + + @pytest.mark.parametrize("check", [False, True]) + def test_present_no_current_object_non_empty_backend(self, mocker, check): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(404, ""), + http.Response(200, "[{}]"), + ) + + with pytest.raises(errors.Error, match="already active"): + datastore.sync( + "present", client, "/list", "/resource", + {"spec": {"my": "data"}}, check, + ) + + client.put.assert_not_called() + + +class TestDatastore(ModuleTestCase): + def test_minimal_datastore_parameters_present(self, mocker): + sync_mock = mocker.patch.object(datastore, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_datastore", + dsn="my-dsn", + ) + + with pytest.raises(AnsibleExitJson): + datastore.main() + + state, _client, list_path, resource_path, payload, check_mode = ( + sync_mock.call_args[0] + ) + assert state == "present" + assert resource_path == "/api/enterprise/store/v1/provider/test_datastore" + assert list_path == "/api/enterprise/store/v1/provider" + assert payload == dict( + type="PostgresConfig", + api_version="store/v1", + metadata=dict(name="test_datastore"), + spec=dict(dsn="my-dsn"), + ) + assert check_mode is False + + def test_minimal_datastore_parameters_absent(self, mocker): + sync_mock = mocker.patch.object(datastore, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_datastore", + state="absent", + ) + + with pytest.raises(AnsibleExitJson): + datastore.main() + + state, _client, list_path, resource_path, _payload, check_mode = ( + sync_mock.call_args[0] + ) + assert state == "absent" + assert resource_path == "/api/enterprise/store/v1/provider/test_datastore" + assert list_path == "/api/enterprise/store/v1/provider" + assert check_mode is False + + def test_all_datastore_parameters(self, mocker): + sync_mock = mocker.patch.object(datastore, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_datastore", + dsn="my-dsn", + pool_size=543, + ) + + with pytest.raises(AnsibleExitJson): + datastore.main() + + state, _client, list_path, resource_path, payload, check_mode = ( + sync_mock.call_args[0] + ) + assert state == "present" + assert resource_path == "/api/enterprise/store/v1/provider/test_datastore" + assert list_path == "/api/enterprise/store/v1/provider" + assert payload == dict( + type="PostgresConfig", + api_version="store/v1", + metadata=dict(name="test_datastore"), + spec=dict(dsn="my-dsn", pool_size=543), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(datastore, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name="test_datastore", + dsn="my-dsn", + ) + + with pytest.raises(AnsibleFailJson): + datastore.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore_info.py new file mode 100644 index 000000000..4a1efc9c2 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_datastore_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import datastore_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDatastoreInfo(ModuleTestCase): + def test_get_all_datastores(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [dict(spec=1), dict(spec=2)] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + datastore_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/store/v1/provider" + assert context.value.args[0]["objects"] == [1, 2] + + def test_get_single_datastore(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = dict(spec=4) + set_module_args(name="sample-datastore") + + with pytest.raises(AnsibleExitJson) as context: + datastore_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/store/v1/provider/sample-datastore" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_datastore(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-datastore") + + with pytest.raises(AnsibleExitJson) as context: + datastore_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-datastore") + + with pytest.raises(AnsibleFailJson): + datastore_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity.py new file mode 100644 index 000000000..fb098ed87 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity.py @@ -0,0 +1,199 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import entity + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + @pytest.mark.parametrize('current', [ + dict(no=dict(system="here")), + dict(system=dict(here="is")), + ]) + def test_no_system_in_desired(self, current): + assert entity.do_differ(current, {}) is False + + def test_system_keys_not_in_current_are_ignored(self): + assert entity.do_differ( + dict(system=dict(a=1, b=2)), + dict(system=dict(a=1)), + ) is False + + def test_actual_changes_are_detected(self): + assert entity.do_differ( + dict(system=dict(a=1, b=2)), + dict(system=dict(a=2)), + ) is True + + def test_missing_keys_are_detected(self): + assert entity.do_differ( + dict(system=dict(b=2)), + dict(system=dict(a=2)), + ) is True + + @pytest.mark.parametrize("current,desired", [ + ([], None), ([], []), + (["a"], ["a"]), + (["a", "b"], ["b", "a"]), + ]) + def test_no_diff_in_subscriptions(self, current, desired): + assert entity.do_differ( + dict(subscriptions=current), dict(subscriptions=desired), + ) is False + + @pytest.mark.parametrize("current,desired", [ + ([], ["a"]), (["a"], []), + (["a"], ["b"]), + (["a", "b"], ["a", "c"]), + ]) + def test_diff_in_subscriptions(self, current, desired): + print((current, desired)) + assert entity.do_differ( + dict(subscriptions=current), dict(subscriptions=desired), + ) is True + + +class TestEntity(ModuleTestCase): + def test_minimal_entity_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_entity', + entity_class='proxy', + ) + + with pytest.raises(AnsibleExitJson): + entity.main() + + state, _c, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/entities/test_entity' + assert payload == dict( + entity_class='proxy', + metadata=dict( + name='test_entity', + namespace='default', + ), + ) + assert check_mode is False + + def test_minimal_entity_parameters_agent_class(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_entity', + entity_class='agent', + ) + + with pytest.raises(AnsibleExitJson): + entity.main() + + state, _c, path, payload, check_mode, _d = sync_mock.call_args[0] + print(payload) + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/entities/test_entity' + assert payload == dict( + entity_class='agent', + metadata=dict( + name='test_entity', + namespace='default', + ), + subscriptions=['entity:test_entity'], + ) + assert check_mode is False + + def test_all_entity_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_entity', + namespace='my', + state='absent', + entity_class='proxy', + subscriptions=['web', 'prod'], + system=dict( + hostname='test-entity', + os='linux', + platform='ubuntu', + network=dict( + interfaces=[ + dict( + name='lo', + addresses=['127.0.0.1/8', '::1/128'] + ), + dict( + name='eth0', + mac='52:54:00:20:1b:3c', + addresses=['93.184.216.34/24'] + ) + ]) + ), + last_seen=1522798317, + deregister=True, + deregistration_handler='email-handler', + redact=['password', 'pass', 'api_key'], + user='agent' + ) + + with pytest.raises(AnsibleExitJson): + entity.main() + + state, _c, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/entities/test_entity' + assert payload == dict( + entity_class='proxy', + subscriptions=['web', 'prod'], + system=dict( + hostname='test-entity', + os='linux', + platform='ubuntu', + network=dict( + interfaces=[ + dict( + name='lo', + addresses=['127.0.0.1/8', '::1/128'] + ), + dict( + name='eth0', + mac='52:54:00:20:1b:3c', + addresses=['93.184.216.34/24'] + ) + ]) + ), + last_seen=1522798317, + deregister=True, + deregistration=dict(handler='email-handler'), + redact=['password', 'pass', 'api_key'], + user='agent', + metadata=dict( + name='test_entity', + namespace='my' + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_entity', + entity_class='proxy' + ) + + with pytest.raises(AnsibleFailJson): + entity.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity_info.py new file mode 100644 index 000000000..964dcaed0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_entity_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import entity_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestEntityInfo(ModuleTestCase): + def test_get_all_entities(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + entity_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/entities" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_entity(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-entity") + + with pytest.raises(AnsibleExitJson) as context: + entity_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/entities/sample-entity" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_entity(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-entity") + + with pytest.raises(AnsibleExitJson) as context: + entity_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-entity") + + with pytest.raises(AnsibleFailJson): + entity_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator.py new file mode 100644 index 000000000..982c97c57 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator.py @@ -0,0 +1,132 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import etcd_replicator + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestEtcdReplicator(ModuleTestCase): + @pytest.mark.parametrize("params", [ + {"name": "demo", "state": "absent"}, + {"name": "demo", "insecure": True, "url": "url", "resource": "resource"}, + { + "name": "demo", + "url": "url", + "resource": "resource", + "ca_cert": "ca_cert", + "cert": "cert", + "key": "key", + }, + ]) + def test_minimal_parameters(self, mocker, params): + mocker.patch.object(utils, "sync_v1").return_value = True, {} + set_module_args(**params) + + with pytest.raises(AnsibleExitJson): + etcd_replicator.main() + + def test_all_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.return_value = True, {} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + state="present", + name="demo", + ca_cert="ca_cert", + cert="cert", + key="key", + insecure=True, + url=["a", "b"], + api_version="api_version", + resource="resource", + namespace="namespace", + replication_interval=30, + ) + + with pytest.raises(AnsibleExitJson): + etcd_replicator.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/enterprise/federation/v1/etcd-replicators/demo" + assert payload == dict( + type="EtcdReplicator", + api_version="federation/v1", + metadata=dict(name="demo"), + spec=dict( + ca_cert="ca_cert", + cert="cert", + key="key", + insecure=True, + url="a,b", + api_version="api_version", + resource="resource", + namespace="namespace", + replication_interval_seconds=30, + ), + ) + assert check_mode is False + + @pytest.mark.parametrize("skip", ["ca_cert", "cert", "key", "url", "resource"]) + def test_missing_required_param_present_secure(self, mocker, skip): + sync_mock = mocker.patch.object(utils, "sync_v1") + all_args = dict( + name="demo", + ca_cert="ca_cert", + cert="cert", + key="key", + url="url", + resource="resource", + ) + set_module_args(**dict((k, v) for k, v in all_args.items() if k != skip)) + + with pytest.raises(AnsibleFailJson): + etcd_replicator.main() + + sync_mock.assert_not_called() + + @pytest.mark.parametrize("skip", ["url", "resource"]) + def test_missing_required_param_present_insecure(self, mocker, skip): + sync_mock = mocker.patch.object(utils, "sync_v1") + all_args = dict( + name="demo", + insecure=True, + url="url", + resource="resource", + ) + set_module_args(**dict((k, v) for k, v in all_args.items() if k != skip)) + + with pytest.raises(AnsibleFailJson): + etcd_replicator.main() + + sync_mock.assert_not_called() + + def test_failure(self, mocker): + mocker.patch.object(utils, "sync_v1").side_effect = ( + errors.Error("Bad error") + ) + set_module_args(name="demo", state="absent") + + with pytest.raises(AnsibleFailJson): + etcd_replicator.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator_info.py new file mode 100644 index 000000000..e651dab76 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_etcd_replicator_info.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import etcd_replicator_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestEtcdReplicatorInfo(ModuleTestCase): + def test_all_parameters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k1": "v1"}} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + name="demo", + ) + + with pytest.raises(AnsibleExitJson): + etcd_replicator_info.main() + + def test_get_all_secrets(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [ + {"spec": {"k1": "v1"}}, {"spec": {"k2": "v2"}}, + ] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + etcd_replicator_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/federation/v1/etcd-replicators" + assert context.value.args[0]["objects"] == [ + {"k1": "v1"}, {"k2": "v2"}, + ] + + def test_get_single_replicator(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k3": "v3"}} + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + etcd_replicator_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/federation/v1/etcd-replicators/demo" + assert context.value.args[0]["objects"] == [{"k3": "v3"}] + + def test_missing_single_replicator(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + etcd_replicator_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="demo") + + with pytest.raises(AnsibleFailJson): + etcd_replicator_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event.py new file mode 100644 index 000000000..8808ec2c5 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event.py @@ -0,0 +1,286 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, +) +from ansible_collections.sensu.sensu_go.plugins.modules import event + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestGetObjects: + def test_get_entity(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"entity": "entity"}') + resp = event.get_entity(client, 'default', 'entity') + + assert resp == {'entity': 'entity'} + + def test_get_entity_404(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, '') + + with pytest.raises(errors.SyncError, + match="Entity with name 'entity' does not exist on remote."): + event.get_entity(client, 'default', 'entity') + + def test_get_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"check": "check"}') + resp = event.get_check(client, 'default', 'check') + + assert resp == {'check': 'check'} + + def test_get_check_404(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(404, '') + + with pytest.raises(errors.SyncError, + match="Check with name 'check' does not exist on remote."): + event.get_check(client, 'default', 'check') + + +class TestEvent(ModuleTestCase): + def test_missing_entity_on_remote(self, mocker): + get_entity_mock = mocker.patch.object(event, 'get_entity') + get_entity_mock.side_effect = errors.SyncError('Error') + + set_module_args( + entity='awesome_entity', + check='awesome_check', + ) + + with pytest.raises(AnsibleFailJson, match='Error'): + event.main() + + def test_missing_check_on_remote(self, mocker): + mocker.patch.object(event, 'get_entity') + get_check_mock = mocker.patch.object(event, 'get_check') + get_check_mock.side_effect = errors.SyncError('Error') + + set_module_args( + entity='awesome_entity', + check='awesome_check', + ) + + with pytest.raises(AnsibleFailJson, match='Error'): + event.main() + + def test_minimal_event_parameters(self, mocker): + send_event_mock = mocker.patch.object(event, 'send_event') + send_event_mock.return_value = True, {} + get_entity_mock = mocker.patch.object(event, 'get_entity') + get_entity_mock.return_value = dict( + metadata=dict( + name='awesome_entity', + namespace='default' + ), + entity_class='proxy' + ) + get_check_mock = mocker.patch.object(event, 'get_check') + get_check_mock.return_value = dict( + metadata=dict( + name='awesome_check', + namespace='default' + ) + ) + + set_module_args( + entity='awesome_entity', + check='awesome_check', + ) + + with pytest.raises(AnsibleExitJson): + event.main() + + _client, path, payload, check_mode = send_event_mock.call_args[0] + assert path == '/api/core/v2/namespaces/default/events/awesome_entity/awesome_check' + assert payload == dict( + metadata=dict( + namespace='default' + ), + entity=dict( + metadata=dict( + name='awesome_entity', + namespace='default' + ), + entity_class='proxy' + ), + check=dict( + metadata=dict( + name='awesome_check', + namespace='default' + ) + ) + ) + assert check_mode is False + + def test_all_event_parameters(self, mocker): + entity_object = dict( + metadata=dict( + name='awesome_entity', + namespace='default' + ), + entity_class='proxy' + ) + check_object = dict( + metadata=dict( + name='awesome_check', + namespace='default' + ), + command="check-cpu.sh -w 75 -c 90", + handlers=["slack"], + interval=60, + publish=True, + subscriptions=["linux"], + ) + send_event_mock = mocker.patch.object(event, 'send_event') + send_event_mock.return_value = True, {} + get_entity_mock = mocker.patch.object(event, 'get_entity') + get_entity_mock.return_value = entity_object + get_check_mock = mocker.patch.object(event, 'get_check') + get_check_mock.return_value = check_object + + set_module_args( + namespace='my', + timestamp=1234567, + entity='awesome_entity', + check='awesome_check', + check_attributes=dict( + duration=1.945, + executed=1522100915, + history=[ + dict( + executed=1552505193, + status=1 + ), + dict( + executed=1552505293, + status=0 + ), + dict( + executed=1552505393, + status=0 + ), + dict( + executed=1552505493, + status=0 + ) + ], + issued=1552506033, + last_ok=1552506033, + output='10', + state='passing', + status='ok', + total_state_change=0 + ), + metric_attributes=dict( + handlers=['handler1', 'handler2'], + points=[{ + 'name': 'sensu-go-sandbox.curl_timings.time_total', + 'tags': [], + 'timestamp': 1552506033, + 'value': 0.005 + }, { + 'name': 'sensu-go-sandbox.curl_timings.time_namelookup', + 'tags': [], + 'timestamp': 1552506033, + 'value': 0.004 + }] + ) + ) + + with pytest.raises(AnsibleExitJson): + event.main() + + _client, path, payload, check_mode = send_event_mock.call_args[0] + assert path == '/api/core/v2/namespaces/my/events/awesome_entity/awesome_check' + assert payload == dict( + metadata=dict( + namespace='my' + ), + timestamp=1234567, + entity=dict( + metadata=dict( + name='awesome_entity', + namespace='default' + ), + entity_class='proxy' + ), + check=dict( + metadata=dict( + name='awesome_check', + namespace='default' + ), + command="check-cpu.sh -w 75 -c 90", + handlers=["slack"], + interval=60, + publish=True, + subscriptions=["linux"], + duration=1.945, + executed=1522100915, + history=[ + dict( + executed=1552505193, + status=1 + ), + dict( + executed=1552505293, + status=0 + ), + dict( + executed=1552505393, + status=0 + ), + dict( + executed=1552505493, + status=0 + ) + ], + issued=1552506033, + last_ok=1552506033, + output='10', + state='passing', + status=0, + total_state_change=0 + ), + metrics=dict( + handlers=['handler1', 'handler2'], + points=[{ + 'name': 'sensu-go-sandbox.curl_timings.time_total', + 'tags': [], + 'timestamp': 1552506033, + 'value': 0.005 + }, { + 'name': 'sensu-go-sandbox.curl_timings.time_namelookup', + 'tags': [], + 'timestamp': 1552506033, + 'value': 0.004 + }] + ) + ) + assert check_mode is False + + def test_failure(self, mocker): + get_entity_mock = mocker.patch.object(event, 'get_entity') + get_entity_mock.side_effect = errors.Error('Bad error') + set_module_args( + entity='awesome_entity', + check=dict( + name='awesome_check' + ) + ) + + with pytest.raises(AnsibleFailJson): + event.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event_info.py new file mode 100644 index 000000000..c9270892d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_event_info.py @@ -0,0 +1,94 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import event_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestEventInfo(ModuleTestCase): + def test_get_all_events(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + event_info.main() + + _client, path = get_mock.call_args[0] + assert path == '/api/core/v2/namespaces/my/events' + assert context.value.args[0]['objects'] == [1, 2, 3] + + def test_get_events_by_entity(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = [1, 2] + set_module_args( + entity='simple-entity' + ) + + with pytest.raises(AnsibleExitJson) as context: + event_info.main() + + _client, path = get_mock.call_args[0] + assert path == '/api/core/v2/namespaces/default/events/simple-entity' + assert context.value.args[0]['objects'] == [1, 2] + + def test_get_events_by_check(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = [1, 2] + set_module_args( + check='simple-check' + ) + + with pytest.raises(AnsibleFailJson, + match=r"missing parameter\(s\) required by 'check': entity"): + event_info.main() + + def test_get_single_event_by_entity_and_check(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = 4 + set_module_args( + entity='simple-entity', + check='simple-check' + ) + + with pytest.raises(AnsibleExitJson) as context: + event_info.main() + + _client, path = get_mock.call_args[0] + assert path == '/api/core/v2/namespaces/default/events/simple-entity/simple-check' + assert context.value.args[0]['objects'] == [4] + + def test_no_event_by_entity_and_check(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args( + entity='simple-entity', + check='simple-check' + ) + + with pytest.raises(AnsibleExitJson) as context: + event_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.side_effect = errors.Error('Bad error') + set_module_args(entity='simple-entity') + + with pytest.raises(AnsibleFailJson): + event_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter.py new file mode 100644 index 000000000..f92a52623 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter.py @@ -0,0 +1,91 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import filter + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestFilter(ModuleTestCase): + def test_minimal_filter_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_filter', + action='allow', + expressions='event.check.occurences == 1', + ) + + with pytest.raises(AnsibleExitJson): + filter.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/filters/test_filter' + assert payload == dict( + action='allow', + expressions=['event.check.occurences == 1'], + metadata=dict( + name='test_filter', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_filter_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_filter', + namespace='my', + state='absent', + action='allow', + expressions='event.check.occurences == 1', + runtime_assets='awesomeness', + labels={'region': 'us-west-1'}, + annotations={'playbook': 12345}, + ) + + with pytest.raises(AnsibleExitJson): + filter.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/filters/test_filter' + assert payload == dict( + action='allow', + expressions=['event.check.occurences == 1'], + runtime_assets=['awesomeness'], + metadata=dict( + name='test_filter', + namespace='my', + labels={'region': 'us-west-1'}, + annotations={'playbook': '12345'}, + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_filter', + action='deny', + expressions='event.check.occurences == 1', + ) + + with pytest.raises(AnsibleFailJson): + filter.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter_info.py new file mode 100644 index 000000000..d8bbcf41a --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_filter_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import filter_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestFilterInfo(ModuleTestCase): + def test_get_all_filters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + filter_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/filters" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_filter(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-filter") + + with pytest.raises(AnsibleExitJson) as context: + filter_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/filters/sample-filter" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_filter(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-filter") + + with pytest.raises(AnsibleExitJson) as context: + filter_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-filter") + + with pytest.raises(AnsibleFailJson): + filter_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_handler_set.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_handler_set.py new file mode 100644 index 000000000..bd6aa8059 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_handler_set.py @@ -0,0 +1,59 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import handler_set + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestHandlerSet(ModuleTestCase): + def test_all_handler_set_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name='test_handler', + namespace='my', + state='absent', + handlers=['tcp_handler', 'udp_handler'] + ) + + with pytest.raises(AnsibleExitJson): + handler_set.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "absent" + assert path == "/api/core/v2/namespaces/my/handlers/test_handler" + assert payload == dict( + type='set', + handlers=['tcp_handler', 'udp_handler'], + metadata=dict( + name="test_handler", + namespace="my", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_handler', + state='absent', + handlers=['tcp_handler', 'udp_handler'] + ) + + with pytest.raises(AnsibleFailJson): + handler_set.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook.py new file mode 100644 index 000000000..3b6e6da8d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook.py @@ -0,0 +1,93 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import hook + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestHook(ModuleTestCase): + def test_minimal_hook_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_hook', + command='/bin/true', + timeout=10, + ) + + with pytest.raises(AnsibleExitJson): + hook.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/hooks/test_hook' + assert payload == dict( + command='/bin/true', + timeout=10, + metadata=dict( + name='test_hook', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_hook_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_hook', + namespace='my', + state='absent', + command='/bin/true', + timeout=30, + stdin=True, + runtime_assets='awesomeness', + labels={'region': 'us-west-1'}, + annotations={'playbook': 12345}, + ) + + with pytest.raises(AnsibleExitJson): + hook.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/hooks/test_hook' + assert payload == dict( + command='/bin/true', + timeout=30, + stdin=True, + runtime_assets=['awesomeness'], + metadata=dict( + name='test_hook', + namespace='my', + labels={'region': 'us-west-1'}, + annotations={'playbook': '12345'}, + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_hook', + command='/bin/true', + timeout=10 + ) + + with pytest.raises(AnsibleFailJson): + hook.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook_info.py new file mode 100644 index 000000000..82269840f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_hook_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import hook_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestHookInfo(ModuleTestCase): + def test_get_all_hooks(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + hook_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/hooks" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_hook(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-hook") + + with pytest.raises(AnsibleExitJson) as context: + hook_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/hooks/sample-hook" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_hook(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-hook") + + with pytest.raises(AnsibleExitJson) as context: + hook_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-hook") + + with pytest.raises(AnsibleFailJson): + hook_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ldap_auth_provider.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ldap_auth_provider.py new file mode 100644 index 000000000..4b9e1c463 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_ldap_auth_provider.py @@ -0,0 +1,326 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, + utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import ldap_auth_provider + +from .common.utils import ( + AnsibleExitJson, + AnsibleFailJson, + ModuleTestCase, + set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + def test_no_changes(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict(name="openldap"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="openldap", + created_by="me", + ), + ) + + assert ldap_auth_provider.do_differ(current, desired) is False + + def test_changes_are_detected(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=636, + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict(name="openldap"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="openldap", + created_by="me", + ), + ) + assert ldap_auth_provider.do_differ(current, desired) is True + + def test_changes_are_detected_diff_servers_len(self): + desired = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ), + dict( + host="127.0.0.2", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ), + ], + ), + metadata=dict(name="openldap"), + ) + current = dict( + spec=dict( + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ), + metadata=dict( + name="openldap", + created_by="me", + ), + ) + assert ldap_auth_provider.do_differ(current, desired) is True + + def test_changes_are_other_params(self): + desired = dict( + spec=dict( + servers=[], + groups_prefix="ldap", + username_prefix="ldap", + ), + metadata=dict(name="openldap"), + ) + current = dict( + spec=dict( + servers=[], + ), + metadata=dict( + name="openldap", + created_by="me", + ), + ) + assert ldap_auth_provider.do_differ(current, desired) is True + + +class TestLDAPAutProvider(ModuleTestCase): + def test_minimal_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="openldap", + servers=[ + dict( + host="127.0.0.1", + group_search=dict( + base_dn="dc=acme,dc=org", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + ), + ) + ], + ) + + with pytest.raises(AnsibleExitJson): + ldap_auth_provider.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[ + 0 + ] + + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/openldap" + assert payload == dict( + type="ldap", + api_version="authentication/v2", + metadata=dict(name="openldap"), + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=None, + insecure=False, + security="tls", + trusted_ca_file=None, + client_cert_file=None, + client_key_file=None, + binding=None, + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="groupOfNames", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="uid", + name_attribute="cn", + object_class="person", + ), + ) + ] + ), + ) + + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="openldap", + servers=[ + dict( + host="127.0.0.1", + port=636, + insecure=False, + security="tls", + trusted_ca_file="/path/to/trusted-certificate-authorities.pem", + client_cert_file="/path/to/ssl/cert.pem", + client_key_file="/path/to/ssl/key.pem", + binding=dict( + user_dn="cn=binder,dc=acme,dc=org", + password="YOUR_PASSWORD", + ), + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="groupOfNames", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="uid", + name_attribute="cn", + object_class="person", + ), + ) + ], + groups_prefix="ldap", + username_prefix="ldap", + ) + + with pytest.raises(AnsibleExitJson): + ldap_auth_provider.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[ + 0 + ] + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/openldap" + assert payload == dict( + type="ldap", + api_version="authentication/v2", + metadata=dict(name="openldap"), + spec=dict( + servers=[ + dict( + host="127.0.0.1", + port=636, + insecure=False, + security="tls", + trusted_ca_file="/path/to/trusted-certificate-authorities.pem", + client_cert_file="/path/to/ssl/cert.pem", + client_key_file="/path/to/ssl/key.pem", + binding=dict( + user_dn="cn=binder,dc=acme,dc=org", + password="YOUR_PASSWORD", + ), + group_search=dict( + base_dn="dc=acme,dc=org", + attribute="member", + name_attribute="cn", + object_class="groupOfNames", + ), + user_search=dict( + base_dn="dc=acme,dc=org", + attribute="uid", + name_attribute="cn", + object_class="person", + ), + ) + ], + groups_prefix="ldap", + username_prefix="ldap", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + ldap_auth_provider.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator.py new file mode 100644 index 000000000..9cef32e76 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator.py @@ -0,0 +1,126 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import mutator + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + @pytest.mark.parametrize("current,desired", [ + ( # No diff in params, no secrets + dict(name="demo"), + dict(name="demo"), + ), + ( # No diff in params, no diff in secrets + dict(name="demo", secrets=[ + dict(name="n1", secret="s1"), dict(name="n2", secret="s2"), + ]), + dict(name="demo", secrets=[ + dict(name="n2", secret="s2"), dict(name="n1", secret="s1"), + ]), + ), + ]) + def test_no_difference(self, current, desired): + assert mutator.do_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired", [ + ( # Diff in params, no diff in secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="a", secret="1")]), + ), + ( # No diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="demo", secrets=[dict(name="b", secret="2")]), + ), + ( # Diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="b", secret="2")]), + ), + ]) + def test_difference(self, current, desired): + assert mutator.do_differ(current, desired) is True + + +class TestMutator(ModuleTestCase): + def test_minimal_mutator_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_mutator', + command='/bin/true', + ) + + with pytest.raises(AnsibleExitJson): + mutator.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/mutators/test_mutator' + assert payload == dict( + command='/bin/true', + metadata=dict( + name='test_mutator', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_mutator_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_mutator', + namespace='my', + state='absent', + command='/bin/true', + timeout=30, + runtime_assets='awesomeness', + labels={'region': 'us-west-1'}, + annotations={'playbook': 12345}, + secrets=[dict(name="a", secret="b")], + ) + + with pytest.raises(AnsibleExitJson): + mutator.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/mutators/test_mutator' + assert payload == dict( + command='/bin/true', + timeout=30, + runtime_assets=['awesomeness'], + metadata=dict( + name='test_mutator', + namespace='my', + labels={'region': 'us-west-1'}, + annotations={'playbook': '12345'}, + ), + secrets=[dict(name="a", secret="b")], + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_mutator', + command='/bion/true' + ) + + with pytest.raises(AnsibleFailJson): + mutator.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator_info.py new file mode 100644 index 000000000..b1456c8fb --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_mutator_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import mutator_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestMutatorInfo(ModuleTestCase): + def test_get_all_mutators(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + mutator_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/mutators" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_mutator(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-mutator") + + with pytest.raises(AnsibleExitJson) as context: + mutator_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/mutators/sample-mutator" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_mutator(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-mutator") + + with pytest.raises(AnsibleExitJson) as context: + mutator_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-mutator") + + with pytest.raises(AnsibleFailJson): + mutator_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace.py new file mode 100644 index 000000000..e51a63af1 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import namespace + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestNamespace(ModuleTestCase): + def test_namespace(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='dev' + ) + + with pytest.raises(AnsibleExitJson): + namespace.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/dev' + assert payload == dict( + name='dev' + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='dev', + ) + + with pytest.raises(AnsibleFailJson): + namespace.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace_info.py new file mode 100644 index 000000000..431b6463f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_namespace_info.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import namespace_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestNamespaceInfo(ModuleTestCase): + def test_get_namespaces(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + namespace_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + namespace_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_oidc_auth_provider.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_oidc_auth_provider.py new file mode 100644 index 000000000..0780cdc2f --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_oidc_auth_provider.py @@ -0,0 +1,118 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, + utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import oidc_auth_provider + +from .common.utils import ( + AnsibleExitJson, + AnsibleFailJson, + ModuleTestCase, + set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestADAutProvider(ModuleTestCase): + def test_minimal_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="oidc_name", + additional_scopes=["openid"], + client_id="a8e43af034e7f2608780", + client_secret="b63968394be6ed2edb61c93847ee792f31bf6216", + disable_offline_access=False, + server="https://oidc.example.com:9031", + username_claim="email", + ) + + with pytest.raises(AnsibleExitJson): + oidc_auth_provider.main() + + state, _client, path, payload, check_mode = sync_v1_mock.call_args[ + 0 + ] + + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/oidc_name" + assert payload == dict( + type="oidc", + api_version="authentication/v2", + metadata=dict(name="oidc_name"), + spec=dict( + additional_scopes=["openid"], + client_id="a8e43af034e7f2608780", + client_secret="b63968394be6ed2edb61c93847ee792f31bf6216", + disable_offline_access=False, + server="https://oidc.example.com:9031", + username_claim="email", + ), + ) + + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, "sync_v1") + sync_v1_mock.return_value = True, {} + set_module_args( + state="present", + name="oidc_name", + additional_scopes=["groups", "email", "username"], + client_id="a8e43af034e7f2608780", + client_secret="b63968394be6ed2edb61c93847ee792f31bf6216", + disable_offline_access=False, + redirect_uri="http://127.0.0.1:8080/api/enterprise/authentication/v2/oidc/callback", + server="https://oidc.example.com:9031", + groups_claim="groups", + groups_prefix="oidc:", + username_claim="email", + username_prefix="oidc:", + ) + + with pytest.raises(AnsibleExitJson): + oidc_auth_provider.main() + + state, _client, path, payload, check_mode = sync_v1_mock.call_args[ + 0 + ] + assert state == "present" + assert path == "/api/enterprise/authentication/v2/authproviders/oidc_name" + assert payload == dict( + type="oidc", + api_version="authentication/v2", + metadata=dict(name="oidc_name"), + spec=dict( + additional_scopes=["groups", "email", "username"], + client_id="a8e43af034e7f2608780", + client_secret="b63968394be6ed2edb61c93847ee792f31bf6216", + disable_offline_access=False, + redirect_uri="http://127.0.0.1:8080/api/enterprise/authentication/v2/oidc/callback", + server="https://oidc.example.com:9031", + groups_claim="groups", + groups_prefix="oidc:", + username_claim="email", + username_prefix="oidc:", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + oidc_auth_provider.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler.py new file mode 100644 index 000000000..69c3254a3 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler.py @@ -0,0 +1,136 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import pipe_handler + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + @pytest.mark.parametrize("current,desired", [ + ( # No diff in params, no secrets + dict(name="demo"), + dict(name="demo"), + ), + ( # No diff in params, no diff in secrets + dict(name="demo", secrets=[ + dict(name="n1", secret="s1"), dict(name="n2", secret="s2"), + ]), + dict(name="demo", secrets=[ + dict(name="n2", secret="s2"), dict(name="n1", secret="s1"), + ]), + ), + ]) + def test_no_difference(self, current, desired): + assert pipe_handler.do_differ(current, desired) is False + + @pytest.mark.parametrize("current,desired", [ + ( # Diff in params, no diff in secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="a", secret="1")]), + ), + ( # No diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="demo", secrets=[dict(name="b", secret="2")]), + ), + ( # Diff in params, missing and set secrets + dict(name="demo", secrets=[dict(name="a", secret="1")]), + dict(name="prod", secrets=[dict(name="b", secret="2")]), + ), + ]) + def test_difference(self, current, desired): + assert pipe_handler.do_differ(current, desired) is True + + +class TestPipeHandler(ModuleTestCase): + def test_minimal_pipe_handler_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_handler", + command='echo "test"' + ) + + with pytest.raises(AnsibleExitJson): + pipe_handler.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/default/handlers/test_handler" + assert payload == dict( + command='echo "test"', + type='pipe', + metadata=dict( + name="test_handler", + namespace="default", + ), + ) + assert check_mode is False + + def test_all_pipe_handler_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name='test_handler', + namespace='my', + state='absent', + command='/bin/true', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + env_vars=dict(foo='bar'), + runtime_assets='awesomeness', + secrets=[dict(name="a", secret="b")], + ) + + with pytest.raises(AnsibleExitJson): + pipe_handler.main() + + state, _client, path, payload, check_mode, _d = sync_mock.call_args[0] + assert state == "absent" + assert path == "/api/core/v2/namespaces/my/handlers/test_handler" + assert payload == dict( + command='/bin/true', + type='pipe', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + env_vars=['foo=bar'], + runtime_assets=['awesomeness'], + metadata=dict( + name="test_handler", + namespace="my", + ), + secrets=[dict(name="a", secret="b")], + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_handler', + state='absent', + command='/bin/true', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + env_vars=dict(foo='bar'), + runtime_assets='awesomeness' + ) + + with pytest.raises(AnsibleFailJson): + pipe_handler.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler_info.py new file mode 100644 index 000000000..2339e8551 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_pipe_handler_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import handler_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestHandlerInfo(ModuleTestCase): + def test_get_all_handlers(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + handler_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/handlers" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_handler(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-handler") + + with pytest.raises(AnsibleExitJson) as context: + handler_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/handlers/sample-handler" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_handler(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-handler") + + with pytest.raises(AnsibleExitJson) as context: + handler_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-handler") + + with pytest.raises(AnsibleFailJson): + handler_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role.py new file mode 100644 index 000000000..cd1ce4fbf --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role.py @@ -0,0 +1,142 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import role + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestRole(ModuleTestCase): + def test_minimal_role_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role', + rules=[ + dict( + verbs=[], + resources=[] + ), + ] + ) + + with pytest.raises(AnsibleExitJson): + role.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/roles/test_role' + assert payload == dict( + rules=[ + dict( + verbs=[], + resources=[], + resource_names=None, + ) + ], + metadata=dict( + name='test_role', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_role_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role', + namespace='my', + state='present', + rules=[ + dict( + verbs=['get', 'list', 'create'], + resources=['assets', 'entities'], + ), + dict( + verbs=['list'], + resources=['check'], + resource_names=['my-check'], + ) + ], + ) + + with pytest.raises(AnsibleExitJson): + role.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/my/roles/test_role' + assert payload == dict( + metadata=dict( + name='test_role', + namespace='my', + ), + rules=[ + dict( + verbs=['get', 'list', 'create'], + resources=['assets', 'entities'], + resource_names=None, + ), + dict( + verbs=['list'], + resources=['check'], + resource_names=['my-check'], + ) + ], + ) + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_role', + rules=[ + dict( + verbs=[], + resources=[], + ) + ], + ) + with pytest.raises(AnsibleFailJson): + role.main() + + def test_failure_invalid_verb(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_role', + rules=[ + dict( + verbs=['list', 'invalid'], + resources=[], + ), + ] + ) + + with pytest.raises(AnsibleFailJson): + role.main() + + def test_failure_empty_rules(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_role', + rules=[] + ) + + with pytest.raises(AnsibleFailJson): + role.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding.py new file mode 100644 index 000000000..b22eaa7ef --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding.py @@ -0,0 +1,214 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import role_binding + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestRoleBinding(ModuleTestCase): + def test_minimal_role_binding_parameters_users(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role_binding', + role='test_role', + users=['test_user'], + ) + + with pytest.raises(AnsibleExitJson): + role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/rolebindings/test_role_binding' + assert payload == dict( + role_ref=dict( + name='test_role', + type='Role', + ), + subjects=[ + dict( + name='test_user', + type='User', + ), + ], + metadata=dict( + name='test_role_binding', + namespace='default', + ), + ) + assert check_mode is False + + def test_minimal_role_binding_parameters_groups(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role_binding', + role='test_role', + groups=['test_group'], + ) + + with pytest.raises(AnsibleExitJson): + role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/rolebindings/test_role_binding' + assert payload == dict( + role_ref=dict( + name='test_role', + type='Role', + ), + subjects=[ + dict( + name='test_group', + type='Group', + ), + ], + metadata=dict( + name='test_role_binding', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_role_binding_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role_binding', + namespace='my', + role='test_role', + users=['user_1', 'user_2'], + groups=['group_1', 'group_2'], + ) + + with pytest.raises(AnsibleExitJson): + role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/my/rolebindings/test_role_binding' + assert payload == dict( + metadata=dict( + name='test_role_binding', + namespace='my', + ), + role_ref=dict( + name='test_role', + type='Role', + ), + subjects=[ + dict( + name='group_1', + type='Group', + ), + dict( + name='group_2', + type='Group', + ), + dict( + name='user_1', + type='User', + ), + dict( + name='user_2', + type='User', + ), + ] + ) + assert check_mode is False + + def test_role_binding_with_cluster_role(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_role_binding', + cluster_role='test_cluster_role', + users=['test_user'], + ) + + with pytest.raises(AnsibleExitJson): + role_binding.main() + + state, _client, path, payload, check_mode, _compare = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/rolebindings/test_role_binding' + assert payload == dict( + role_ref=dict( + name='test_cluster_role', + type='ClusterRole', + ), + subjects=[ + dict( + name='test_user', + type='User', + ), + ], + metadata=dict( + name='test_role_binding', + namespace='default', + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_role_binding', + role='test_role', + users=['test_user'], + ) + with pytest.raises(AnsibleFailJson): + role_binding.main() + + def test_failure_role_and_cluster_role(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_role_binding', + role='test_role', + cluster_role='test_cluster_role', + ) + + with pytest.raises(AnsibleFailJson): + role_binding.main() + + def test_failure_missing_groups_or_users(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = Exception("Validation should fail but didn't") + set_module_args( + name='test_role_binding', + role='test_role', + ) + + with pytest.raises(AnsibleFailJson): + role_binding.main() + + @pytest.mark.parametrize("params,result", [ + ( + dict(role="test-role", cluster_role=None), + ("Role", "test-role"), + ), + ( + dict(cluster_role="test-cluster-role", role=None), + ("ClusterRole", "test-cluster-role"), + ), + ]) + def test_infer_role(self, params, result): + assert result == role_binding.infer_role(params) diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding_info.py new file mode 100644 index 000000000..2f7725160 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_binding_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import role_binding_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestRoleBindingInfo(ModuleTestCase): + def test_get_all_role_bindings(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + role_binding_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/rolebindings" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_role_binding(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 1 + set_module_args(name="test-role-binding") + + with pytest.raises(AnsibleExitJson) as context: + role_binding_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/rolebindings/test-role-binding" + assert context.value.args[0]["objects"] == [1] + + def test_missing_single_role_binding(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-role-binding") + + with pytest.raises(AnsibleExitJson) as context: + role_binding_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-role-binding") + + with pytest.raises(AnsibleFailJson): + role_binding_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_info.py new file mode 100644 index 000000000..a2bec291d --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_role_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import role_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestRoleInfo(ModuleTestCase): + def test_get_all_roles(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + role_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/roles" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_role(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 1 + set_module_args(name="test-role") + + with pytest.raises(AnsibleExitJson) as context: + role_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/roles/test-role" + assert context.value.args[0]["objects"] == [1] + + def test_missing_single_role(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-role") + + with pytest.raises(AnsibleExitJson) as context: + role_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-role") + + with pytest.raises(AnsibleFailJson): + role_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret.py new file mode 100644 index 000000000..5912ae738 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secret + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSecret(ModuleTestCase): + @pytest.mark.parametrize("params", [ + {"name": "demo", "provider": "env", "id": "MY_VAR"}, + {"name": "demo", "state": "absent"}, + ]) + def test_minimal_parameters(self, mocker, params): + mocker.patch.object(utils, "sync_v1").return_value = True, {} + set_module_args(**params) + + with pytest.raises(AnsibleExitJson): + secret.main() + + def test_all_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync_v1") + sync_mock.return_value = True, {} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + state="present", + name="demo", + namespace="ns", + provider="env", + id="MY_ENV_VAR", + ) + + with pytest.raises(AnsibleExitJson): + secret.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/enterprise/secrets/v1/namespaces/ns/secrets/demo" + assert payload == dict( + type="Secret", + api_version="secrets/v1", + metadata=dict(name="demo", namespace="ns"), + spec=dict(provider="env", id="MY_ENV_VAR"), + ) + assert check_mode is False + + @pytest.mark.parametrize("skip", ["provider", "id"]) + def test_missing_required_param_present(self, mocker, skip): + sync_mock = mocker.patch.object(utils, "sync_v1") + all_args = dict(name="demo", provider="env", id="X") + set_module_args(**dict((k, v) for k, v in all_args.items() if k != skip)) + + with pytest.raises(AnsibleFailJson): + secret.main() + + sync_mock.assert_not_called() + + def test_failure(self, mocker): + mocker.patch.object(utils, "sync_v1").side_effect = ( + errors.Error("Bad error") + ) + set_module_args(name="demo", state="absent") + + with pytest.raises(AnsibleFailJson): + secret.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret_info.py new file mode 100644 index 000000000..161204f26 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secret_info.py @@ -0,0 +1,86 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secret_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSecretInfo(ModuleTestCase): + def test_all_parameters(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k1": "v1"}} + set_module_args( + auth=dict( + user="user", + password="pass", + url="http://127.0.0.1:1234", + api_key="123-key", + verify=False, + ca_path="/tmp/ca.bundle", + ), + name="demo", + namespace="ns", + ) + + with pytest.raises(AnsibleExitJson): + secret_info.main() + + def test_get_all_secrets(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [ + {"spec": {"k1": "v1"}}, {"spec": {"k2": "v2"}}, + ] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + secret_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/secrets/v1/namespaces/default/secrets" + assert context.value.args[0]["objects"] == [ + {"k1": "v1"}, {"k2": "v2"}, + ] + + def test_get_single_secret(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = {"spec": {"k3": "v3"}} + set_module_args(name="demo", namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + secret_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/secrets/v1/namespaces/my/secrets/demo" + assert context.value.args[0]["objects"] == [{"k3": "v3"}] + + def test_missing_single_secret(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="demo") + + with pytest.raises(AnsibleExitJson) as context: + secret_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="demo") + + with pytest.raises(AnsibleFailJson): + secret_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_env.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_env.py new file mode 100644 index 000000000..cce081bf9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_env.py @@ -0,0 +1,69 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secrets_provider_env + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSecretsProviderEnv(ModuleTestCase): + def test_no_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.return_value = True, {} + set_module_args() + + with pytest.raises(AnsibleExitJson): + secrets_provider_env.main() + + state, _client, path, payload, check_mode = sync_v1_mock.call_args[0] + assert state == 'present' + assert path == '/api/enterprise/secrets/v1/providers/env' + assert payload == dict( + type='Env', + api_version="secrets/v1", + metadata=dict(name='env'), + spec={} + ) + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.return_value = True, {} + set_module_args( + state='present', + ) + + with pytest.raises(AnsibleExitJson): + secrets_provider_env.main() + + state, _client, path, payload, check_mode = sync_v1_mock.call_args[0] + assert state == 'present' + assert path == '/api/enterprise/secrets/v1/providers/env' + assert payload == dict( + type='Env', + api_version="secrets/v1", + metadata=dict(name='env'), + spec={} + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + secrets_provider_env.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_info.py new file mode 100644 index 000000000..695152493 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secrets_provider_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSecretsProviderInfo(ModuleTestCase): + def test_get_all_secrets_providers(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [dict(spec=1), dict(spec=2)] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + secrets_provider_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/secrets/v1/providers" + assert context.value.args[0]["objects"] == [1, 2] + + def test_get_single_secrets_provider(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = dict(spec=4) + set_module_args(name="sample-secrets-provider") + + with pytest.raises(AnsibleExitJson) as context: + secrets_provider_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/enterprise/secrets/v1/providers/sample-secrets-provider" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_secrets_provider(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-secrets-provider") + + with pytest.raises(AnsibleExitJson) as context: + secrets_provider_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-secrets-provider") + + with pytest.raises(AnsibleFailJson): + secrets_provider_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_vault.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_vault.py new file mode 100644 index 000000000..fc4c17a87 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_secrets_provider_vault.py @@ -0,0 +1,182 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import secrets_provider_vault + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestDoDiffer: + def test_fields_are_ignored(self): + desired = dict( + spec=dict( + client=dict( + address="https://my-vault.com", + tls=dict( + ca_cert="cert" + ), + ), + ), + metadata=dict( + name="my-vault" + ) + ) + current = dict( + spec=dict( + client=dict( + address="https://my-vault.com", + agent_address="", # extra field + tls=dict( + ca_cert="cert", + # extra fields + insecure=False, + ca_path="path", + tls_server_name="server", + ) + ), + ), + metadata=dict( + name="my-vault", + created_by="me", + ) + ) + + assert secrets_provider_vault.do_differ(current, desired) is False + + def test_changes_are_detected(self): + desired = dict( + spec=dict( + client=dict( + address="https://my-vault.com", + tls=dict( + ca_cert="cert" + ), + ), + ), + metadata=dict( + name="my-vault" + ) + ) + current = dict( + spec=dict( + client=dict( + address="https://my-vault.com", + tls=dict( + ca_cert="new-cert", + cname="server", + ), + timeout='15s', + ), + ), + metadata=dict( + name="my-vault", + ) + ) + assert secrets_provider_vault.do_differ(current, desired) is True + + +class TestSecretsProviderVault(ModuleTestCase): + def test_minimal_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.return_value = True, {} + set_module_args( + name='my-vault', + state='present', + address='https://my-vault.com', + token='AUTH_TOKEN', + version='v1', + ) + + with pytest.raises(AnsibleExitJson): + secrets_provider_vault.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[0] + assert state == 'present' + assert path == '/api/enterprise/secrets/v1/providers/my-vault' + assert payload == dict( + type='VaultProvider', + api_version="secrets/v1", + metadata=dict(name='my-vault'), + spec=dict( + client=dict( + address='https://my-vault.com', + token='AUTH_TOKEN', + version='v1', + ), + ), + ) + assert check_mode is False + + def test_all_provider_parameters(self, mocker): + sync_v1_mock = mocker.patch.object(utils, 'sync_v1') + sync_v1_mock.return_value = True, {} + set_module_args( + name='my-vault', + state='present', + address='https://my-vault.com', + token='AUTH_TOKEN', + version='v1', + tls=dict( + ca_cert='/etc/ssl/ca.crt', + client_cert='/etc/ssl/client.crt', + client_key='/etc/ssl/client.key', + cname='my-vault.com', + ), + timeout=1, + max_retries=2, + rate_limit=3, + burst_limit=4, + ) + + with pytest.raises(AnsibleExitJson): + secrets_provider_vault.main() + + state, _client, path, payload, check_mode, _do_differ = sync_v1_mock.call_args[0] + assert state == 'present' + assert path == '/api/enterprise/secrets/v1/providers/my-vault' + assert payload == dict( + type='VaultProvider', + api_version="secrets/v1", + metadata=dict(name='my-vault'), + spec=dict( + client=dict( + address='https://my-vault.com', + token='AUTH_TOKEN', + version='v1', + tls=dict( + ca_cert='/etc/ssl/ca.crt', + client_cert='/etc/ssl/client.crt', + client_key='/etc/ssl/client.key', + cname='my-vault.com', + ), + timeout="1s", + max_retries=2, + rate_limiter=dict( + limit=3, + burst=4, + ), + ), + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync_v1') + sync_mock.side_effect = errors.Error("Bad error") + set_module_args() + + with pytest.raises(AnsibleFailJson): + secrets_provider_vault.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence.py new file mode 100644 index 000000000..ad0642718 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence.py @@ -0,0 +1,120 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import silence + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSilence(ModuleTestCase): + def test_minimal_silence_parameters_check(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + check='check' + ) + + with pytest.raises(AnsibleExitJson): + silence.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/silenced/%2A%3Acheck' # %2A = *, %3A = : + assert payload == dict( + check='check', + metadata=dict( + name='*:check', + namespace='default', + ), + ) + assert check_mode is False + + def test_minimal_silence_parameters_subscription(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + subscription='subscription' + ) + + with pytest.raises(AnsibleExitJson): + silence.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'present' + assert path == '/api/core/v2/namespaces/default/silenced/subscription%3A%2A' # %3A = :, %2A = * + assert payload == dict( + subscription='subscription', + metadata=dict( + name='subscription:*', + namespace='default', + ), + ) + assert check_mode is False + + def test_all_silence_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.return_value = True, {} + set_module_args( + namespace='my', + subscription='entity:test-entity', + check='check', + state='absent', + begin=1542671205, + expire=1542771205, + expire_on_resolve=True, + reason='because', + labels={'region': 'us-west-1'}, + annotations={'playbook': 12345}, + ) + + with pytest.raises(AnsibleExitJson): + silence.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == 'absent' + assert path == '/api/core/v2/namespaces/my/silenced/entity%3Atest-entity%3Acheck' # %3A = : + assert payload == dict( + subscription='entity:test-entity', + check='check', + begin=1542671205, + expire=1542771205, + expire_on_resolve=True, + reason='because', + metadata=dict( + name='entity:test-entity:check', + namespace='my', + labels={'region': 'us-west-1'}, + annotations={'playbook': '12345'}, + ), + ) + assert check_mode is False + + def test_failure_when_both_params_are_missing(self): + set_module_args() + + with pytest.raises(AnsibleFailJson, + match='one of the following is required: subscription, check'): + silence.main() + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + subscription='subscription' + ) + + with pytest.raises(AnsibleFailJson): + silence.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence_info.py new file mode 100644 index 000000000..7a2215411 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_silence_info.py @@ -0,0 +1,66 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import silence_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSilenceInfo(ModuleTestCase): + def test_get_all_silences(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args(namespace="my") + + with pytest.raises(AnsibleExitJson) as context: + silence_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/my/silenced" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_silence(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(subscription="subscription") + + with pytest.raises(AnsibleExitJson) as context: + silence_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/namespaces/default/silenced/subscription%3A%2A" # %3A = :, %2A = * + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_silence(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args( + subscription="missing", + check="missing", + ) + + with pytest.raises(AnsibleExitJson) as context: + silence_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(check="check") + + with pytest.raises(AnsibleFailJson): + silence_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_socket_handler.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_socket_handler.py new file mode 100644 index 000000000..b4c72f194 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_socket_handler.py @@ -0,0 +1,101 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import socket_handler + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSocketHandler(ModuleTestCase): + def test_minimal_socket_handler_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name="test_handler", + type='tcp', + host='10.0.1.99', + port=4444 + ) + + with pytest.raises(AnsibleExitJson): + socket_handler.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "present" + assert path == "/api/core/v2/namespaces/default/handlers/test_handler" + assert payload == dict( + type='tcp', + socket=dict( + host='10.0.1.99', + port=4444 + ), + metadata=dict( + name="test_handler", + namespace="default", + ), + ) + assert check_mode is False + + def test_all_socket_handler_parameters(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.return_value = True, {} + set_module_args( + name='test_handler', + namespace='my', + state='absent', + type='udp', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + host='10.0.1.99', + port=4444 + ) + + with pytest.raises(AnsibleExitJson): + socket_handler.main() + + state, _client, path, payload, check_mode = sync_mock.call_args[0] + assert state == "absent" + assert path == "/api/core/v2/namespaces/my/handlers/test_handler" + assert payload == dict( + type='udp', + filters=['occurrences', 'production'], + mutator='only_check_output', + timeout=30, + socket=dict( + host='10.0.1.99', + port=4444 + ), + metadata=dict( + name="test_handler", + namespace="my", + ), + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(utils, "sync") + sync_mock.side_effect = errors.Error("Bad error") + set_module_args( + name='test_handler', + state='absent', + type='udp', + host='10.0.1.99', + port=4444 + ) + + with pytest.raises(AnsibleFailJson): + socket_handler.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_tessen.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_tessen.py new file mode 100644 index 000000000..3eb2796b9 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_tessen.py @@ -0,0 +1,105 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, http, +) +from ansible_collections.sensu.sensu_go.plugins.modules import tessen + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestSync: + def test_remote_and_desired_equal(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + changed, object = tessen.sync(client, "/path", {}, False) + + assert changed is False + assert object == {} + + def test_remote_and_desired_not_equal(self, mocker): + client = mocker.Mock() + client.get.side_effect = ( + http.Response(200, '{"opt_out": "false"}'), + http.Response(200, '{"opt_out": "true"}'), + ) + client.put.return_value = http.Response(200, "") + changed, object = tessen.sync(client, "/path", {'opt_out': True}, False) + + assert changed is True + assert object == {'opt_out': 'true'} + client.put.assert_called_once_with("/path", {'opt_out': True}) + + def test_remote_and_desired_equal_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{}') + changed, object = tessen.sync(client, "/path", {}, True) + + assert changed is False + assert object == {} + + def test_remote_and_desired_not_equal_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"opt_out": "false"}') + changed, object = tessen.sync(client, "/path", {'opt_out': True}, True) + + assert changed is True + assert object == {'opt_out': True} + client.put.assert_not_called() + + +class TestTessen(ModuleTestCase): + def test_enabled(self, mocker): + sync_mock = mocker.patch.object(tessen, 'sync') + sync_mock.return_value = True, {} + set_module_args( + state='enabled' + ) + + with pytest.raises(AnsibleExitJson): + tessen.main() + + _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/tessen' + assert payload == dict( + opt_out=False + ) + assert check_mode is False + + def test_disabled(self, mocker): + sync_mock = mocker.patch.object(tessen, 'sync') + sync_mock.return_value = True, {} + set_module_args( + state='disabled' + ) + + with pytest.raises(AnsibleExitJson): + tessen.main() + + _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/tessen' + assert payload == dict( + opt_out=True + ) + assert check_mode is False + + def test_failure(self, mocker): + sync_mock = mocker.patch.object(tessen, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + state='enabled' + ) + + with pytest.raises(AnsibleFailJson): + tessen.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user.py new file mode 100644 index 000000000..52e4a7698 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user.py @@ -0,0 +1,519 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +from distutils import version + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + arguments, errors, http, utils +) +from ansible_collections.sensu.sensu_go.plugins.modules import user + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestUpdatePassword: + @pytest.mark.parametrize('check', [False, True]) + def test_password_is_valid(self, mocker, check): + client = mocker.Mock() + client.validate_auth_data.return_value = True + + changed = user.update_password(client, '/path', 'user', 'pass', check) + + assert changed is False + client.validate_auth_data.assert_called_once_with('user', 'pass') + client.put.assert_not_called() + + def test_password_is_invalid_older_than_5_21_0(self, mocker): + client = mocker.Mock() + client.validate_auth_data.return_value = False + client.version = version.StrictVersion("5.20.2") + client.put.return_value = http.Response(201, '') + + changed = user.update_password(client, '/path', 'user', 'pass', False) + + assert changed is True + client.validate_auth_data.assert_called_once_with('user', 'pass') + client.put.assert_called_once_with('/path/password', dict( + username='user', password='pass', + )) + + def test_password_is_invalid_5_21_0_or_newer(self, mocker): + client = mocker.Mock() + client.validate_auth_data.return_value = False + client.version = version.StrictVersion("5.21.0") + client.put.return_value = http.Response(201, '') + + changed = user.update_password(client, '/path', 'user', 'pass', False) + + assert changed is True + client.validate_auth_data.assert_called_once_with('user', 'pass') + client.put.assert_called_once() + + path, payload = client.put.call_args[0] + assert path == '/path/reset_password' + assert payload['username'] == 'user' + + # (tadeboro): We cannot validate the value without mocking the bcrypt. + # And I would rather see that our code gets tested by actually using + # the bcrypt rather than mocking it out. This way, the message + # encode/decode stuff gets put through its paces. + assert 'password_hash' in payload + + def test_password_is_invalid_check_mode(self, mocker): + client = mocker.Mock() + client.validate_auth_data.return_value = False + + changed = user.update_password(client, '/path', 'user', 'pass', True) + + assert changed is True + client.validate_auth_data.assert_called_once_with('user', 'pass') + client.put.assert_not_called() + + +class TestUpdatePasswordHash: + @pytest.mark.parametrize('check', [False, True]) + def test_sensu_go_older_than_5_21_0(self, mocker, check): + client = mocker.Mock() + client.version = version.StrictVersion("5.20.0") + + with pytest.raises(errors.SensuError): + user.update_password_hash(client, '/path', 'user', 'hash', check) + + client.put.assert_not_called() + + def test_sensu_go_newer_than_5_21_0(self, mocker): + client = mocker.Mock() + client.version = version.StrictVersion("5.21.0") + client.put.return_value = http.Response(201, '') + + changed = user.update_password_hash( + client, '/path', 'user', 'hash', False, + ) + + assert changed is True + client.put.assert_called_once() + + path, payload = client.put.call_args[0] + assert path == '/path/reset_password' + assert payload['username'] == 'user' + assert payload['password_hash'] == 'hash' + + def test_sensu_go_newer_than_5_21_0_check_mode(self, mocker): + client = mocker.Mock() + client.version = version.StrictVersion("5.21.0") + + changed = user.update_password_hash( + client, '/path', 'user', 'pass', True, + ) + + assert changed is True + client.put.assert_not_called() + + +class TestUpdateGroups: + @pytest.mark.parametrize('check', [False, True]) + def test_update_groups_no_change(self, mocker, check): + client = mocker.Mock() + + result = user.update_groups( + client, '/path', ['a', 'b'], ['b', 'a'], check, + ) + + assert result is False + client.put.assert_not_called() + client.delete.assert_not_called() + + def test_update_groups(self, mocker): + client = mocker.Mock() + client.put.side_effect = [ + http.Response(201, ''), http.Response(201, ''), + ] + client.delete.side_effect = [ + http.Response(204, ''), http.Response(204, ''), + ] + + result = user.update_groups( + client, '/path', ['a', 'b', 'c'], ['e', 'd', 'c'], False, + ) + + assert result is True + client.put.assert_has_calls([ + mocker.call('/path/groups/d', None), + mocker.call('/path/groups/e', None), + ], any_order=True) + client.delete.assert_has_calls([ + mocker.call('/path/groups/a'), + mocker.call('/path/groups/b'), + ], any_order=True) + + def test_update_groups_check_mode(self, mocker): + client = mocker.Mock() + + result = user.update_groups( + client, '/path', ['a', 'b', 'c'], ['e', 'd', 'c'], True, + ) + + assert result is True + client.put.assert_not_called() + client.delete.assert_not_called() + + +class TestUpdateState: + @pytest.mark.parametrize('check', [False, True]) + @pytest.mark.parametrize('state', [False, True]) + def test_update_state_no_change(self, mocker, check, state): + client = mocker.Mock() + + result = user.update_state(client, '/path', state, state, check) + + assert result is False + client.put.assert_not_called() + client.delete.assert_not_called() + + def test_disable_user(self, mocker): + client = mocker.Mock() + client.delete.return_value = http.Response(204, '') + + # Go from disabled == False to disabled == True + result = user.update_state(client, '/path', False, True, False) + + assert result is True + client.put.assert_not_called() + client.delete.assert_called_once_with('/path') + + def test_disable_user_check_mode(self, mocker): + client = mocker.Mock() + + # Go from disabled == False to disabled == True + result = user.update_state(client, '/path', False, True, True) + + assert result is True + client.put.assert_not_called() + client.delete.assert_not_called() + + def test_enable_user(self, mocker): + client = mocker.Mock() + client.put.return_value = http.Response(201, '') + + # Go from disabled == True to disabled == False + result = user.update_state(client, '/path', True, False, False) + + assert result is True + client.put.assert_called_once_with('/path/reinstate', None) + client.delete.assert_not_called() + + def test_enable_user_check_mode(self, mocker): + client = mocker.Mock() + + # Go from disabled == True to disabled == False + result = user.update_state(client, '/path', True, False, True) + + assert result is True + client.put.assert_not_called() + client.delete.assert_not_called() + + +class TestSync: + def test_no_current_object(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + client.put.return_value = http.Response(201, '') + + changed, result = user.sync( + None, client, '/path', {'password': 'data'}, False + ) + + assert changed is True + assert {'new': 'data'} == result + client.put.assert_called_once_with('/path', {'password': 'data'}) + + def test_no_current_object_check(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + + changed, result = user.sync( + None, client, '/path', {'password_hash': 'data'}, True + ) + + assert changed is True + assert {} == result + client.put.assert_not_called() + + def test_password_update(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + p_mock.return_value = True + g_mock = mocker.patch.object(user, 'update_groups') + s_mock = mocker.patch.object(user, 'update_state') + + changed, result = user.sync( + dict(old='data'), client, '/path', + dict(username='user', password='pass'), False + ) + + assert changed is True + assert dict(new='data') == result + p_mock.assert_called_once() + g_mock.assert_not_called() + s_mock.assert_not_called() + + def test_password_update_check_mode(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + p_mock.return_value = False + g_mock = mocker.patch.object(user, 'update_groups') + s_mock = mocker.patch.object(user, 'update_state') + + changed, result = user.sync( + dict(old='data'), client, '/path', + dict(username='user', password='pass'), True + ) + + assert changed is False + assert dict(old='data', username='user') == result + p_mock.assert_called_once() + g_mock.assert_not_called() + s_mock.assert_not_called() + + def test_password_hash_update(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + mock = mocker.patch.object(user, 'update_password_hash') + mock.return_value = True + + changed, result = user.sync( + dict(old='data'), client, '/path', + dict(username='user', password_hash='pass'), False + ) + + assert changed is True + assert dict(new='data') == result + mock.assert_called_once() + + def test_password_hash_update_check_mode(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + mock = mocker.patch.object(user, 'update_password_hash') + mock.return_value = True + + changed, result = user.sync( + dict(old='data'), client, '/path', + dict(username='user', password_hash='pass'), True + ) + + assert changed is True + assert dict(old='data', username='user') == result + mock.assert_called_once() + + def test_when_password_is_set_we_ignore_hash(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + p_mock.return_value = True + h_mock = mocker.patch.object(user, 'update_password_hash') + + user.sync( + dict(old='data'), client, '/path', + dict(username='user', password='pass', password_hash='hash'), + False + ) + + p_mock.assert_called_once() + h_mock.assert_not_called() + + def test_groups_update(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + g_mock = mocker.patch.object(user, 'update_groups') + g_mock.return_value = False + s_mock = mocker.patch.object(user, 'update_state') + + changed, result = user.sync( + dict(groups=['a']), client, '/path', dict(groups=['b']), False + ) + + assert changed is False + assert dict(new='data') == result + p_mock.assert_not_called() + g_mock.assert_called_once() + s_mock.assert_not_called() + + def test_groups_update_check_mode(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + g_mock = mocker.patch.object(user, 'update_groups') + g_mock.return_value = True + s_mock = mocker.patch.object(user, 'update_state') + + changed, result = user.sync( + dict(x=3, groups=['a']), client, '/path', dict(groups=['b']), True + ) + + assert changed is True + assert dict(x=3, groups=['b']) == result + p_mock.assert_not_called() + g_mock.assert_called_once() + s_mock.assert_not_called() + + def test_state_update(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + g_mock = mocker.patch.object(user, 'update_groups') + s_mock = mocker.patch.object(user, 'update_state') + s_mock.return_value = False + + changed, result = user.sync( + dict(disabled=True), client, '/path', dict(disabled=False), False + ) + + assert changed is False + assert dict(new='data') == result + p_mock.assert_not_called() + g_mock.assert_not_called() + s_mock.assert_called_once() + + def test_state_update_check_mode(self, mocker): + client = mocker.Mock() + client.get.return_value = http.Response(200, '{"new": "data"}') + p_mock = mocker.patch.object(user, 'update_password') + g_mock = mocker.patch.object(user, 'update_groups') + s_mock = mocker.patch.object(user, 'update_state') + s_mock.return_value = True + + changed, result = user.sync( + dict(disabled=True), client, '/path', dict(disabled=False), True + ) + + assert changed is True + assert dict(disabled=False) == result + p_mock.assert_not_called() + g_mock.assert_not_called() + s_mock.assert_called_once() + + +class TestUser(ModuleTestCase): + def test_minimal_user_parameters(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = None + sync_mock = mocker.patch.object(user, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='alice', + password='alice!?pass', + ) + + with pytest.raises(AnsibleExitJson): + user.main() + + result, _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/users/alice' + assert payload == dict( + username='alice', + password='alice!?pass', + disabled=False + ) + assert check_mode is False + + def test_minimal_parameters_on_existing_user(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = dict(username='alice') + sync_mock = mocker.patch.object(user, 'sync') + sync_mock.return_value = True, {} + set_module_args(name='alice') + + with pytest.raises(AnsibleExitJson): + user.main() + + result, _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/users/alice' + assert payload == dict(username='alice', disabled=False) + assert check_mode is False + + def test_all_user_parameters(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = None + sync_mock = mocker.patch.object(user, 'sync') + sync_mock.return_value = True, {} + set_module_args( + name='test_user', + state='disabled', + password='password', + groups=['dev', 'ops'], + ) + + with pytest.raises(AnsibleExitJson): + user.main() + + result, _client, path, payload, check_mode = sync_mock.call_args[0] + assert path == '/api/core/v2/users/test_user' + assert payload == dict( + username='test_user', + password='password', + groups=['dev', 'ops'], + disabled=True + ) + assert check_mode is False + + def test_cannot_create_user_without_password(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = None + set_module_args( + name='test_user', + state='disabled', + groups=['dev', 'ops'], + ) + + with pytest.raises(AnsibleFailJson, match='without a password'): + user.main() + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.return_value = None + sync_mock = mocker.patch.object(user, 'sync') + sync_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_user', + password='password' + ) + + with pytest.raises(AnsibleFailJson): + user.main() + + def test_failure_on_initial_get(self, mocker): + get_mock = mocker.patch.object(utils, 'get') + get_mock.side_effect = errors.Error('Bad error') + set_module_args( + name='test_user', + password='password' + ) + + with pytest.raises(AnsibleFailJson): + user.main() + + def test_failure_on_missing_bcrypt_5_21_0_or_newer(self, mocker): + mocker.patch.object(arguments, 'get_sensu_client').return_value = ( + mocker.MagicMock(version='5.22.3') + ) + mocker.patch.object(user, 'HAS_BCRYPT', False) + set_module_args( + name='test_user', + password='password' + ) + + with pytest.raises(AnsibleFailJson, match='bcrypt'): + user.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user_info.py b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user_info.py new file mode 100644 index 000000000..09fc337f0 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/plugins/modules/test_user_info.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.sensu.sensu_go.plugins.module_utils import ( + errors, utils, +) +from ansible_collections.sensu.sensu_go.plugins.modules import user_info + +from .common.utils import ( + AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args, +) + +pytestmark = pytest.mark.skipif( + sys.version_info < (2, 7), reason="requires python2.7 or higher" +) + + +class TestUserInfo(ModuleTestCase): + def test_get_all_users(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = [1, 2, 3] + set_module_args() + + with pytest.raises(AnsibleExitJson) as context: + user_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/users" + assert context.value.args[0]["objects"] == [1, 2, 3] + + def test_get_single_user(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = 4 + set_module_args(name="sample-user") + + with pytest.raises(AnsibleExitJson) as context: + user_info.main() + + _client, path = get_mock.call_args[0] + assert path == "/api/core/v2/users/sample-user" + assert context.value.args[0]["objects"] == [4] + + def test_missing_single_user(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.return_value = None + set_module_args(name="sample-user") + + with pytest.raises(AnsibleExitJson) as context: + user_info.main() + + assert context.value.args[0]["objects"] == [] + + def test_failure(self, mocker): + get_mock = mocker.patch.object(utils, "get") + get_mock.side_effect = errors.Error("Bad error") + set_module_args(name="sample-user") + + with pytest.raises(AnsibleFailJson): + user_info.main() diff --git a/ansible_collections/sensu/sensu_go/tests/unit/requirements.txt b/ansible_collections/sensu/sensu_go/tests/unit/requirements.txt new file mode 100644 index 000000000..7f0b6e759 --- /dev/null +++ b/ansible_collections/sensu/sensu_go/tests/unit/requirements.txt @@ -0,0 +1 @@ +bcrypt |