summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/hashi_vault/tests/unit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
commit38b7c80217c4e72b1d8988eb1e60bb6e77334114 (patch)
tree356e9fd3762877d07cde52d21e77070aeff7e789 /ansible_collections/community/hashi_vault/tests/unit
parentAdding upstream version 7.7.0+dfsg. (diff)
downloadansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.tar.xz
ansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.zip
Adding upstream version 9.4.0+dfsg.upstream/9.4.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/hashi_vault/tests/unit')
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/conftest.py10
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/constraints.txt64
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/fixtures/database_connection_read_response.json20
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/fixtures/database_connections_list_response.json21
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/fixtures/database_role_read_response.json22
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/fixtures/database_roles_list_response.json21
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_role_get_credentials_response.json25
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_role_read_response.json18
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_roles_list_response.json21
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_token_create.py13
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_write.py47
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_aws_iam.py2
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_azure.py12
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_token.py6
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_hashi_vault_auth_method_base.py14
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/option_adapter/test_hashi_vault_option_adapter.py74
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_connection_options.py4
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_helper.py10
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_configure.py195
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_delete.py181
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_read.py200
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_reset.py181
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connections_list.py228
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_create.py197
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_delete.py180
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_read.py200
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_roles_list.py228
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_rotate_root_credentials.py197
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_create.py192
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_get_credentials.py202
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_read.py200
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_rotate_credentials.py199
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_roles_list.py228
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_pki_generate_certificate.py2
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_write.py58
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/authentication/conftest.py10
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/authentication/test_auth_token.py54
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/base/test_hashi_vault_lookup_base.py16
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/conftest.py29
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/test_hashi_vault_option_adapter.py2
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/test_hashi_vault_common_stringify.py48
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/test_hashi_vault_helper.py58
-rw-r--r--ansible_collections/community/hashi_vault/tests/unit/requirements.txt18
43 files changed, 3428 insertions, 279 deletions
diff --git a/ansible_collections/community/hashi_vault/tests/unit/conftest.py b/ansible_collections/community/hashi_vault/tests/unit/conftest.py
index 862e93cf6..7b07c1b1e 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/conftest.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/conftest.py
@@ -78,5 +78,15 @@ def patch_get_vault_client(vault_client):
def requests_unparseable_response():
r = mock.MagicMock()
r.json.side_effect = json.JSONDecodeError
+ return r
+
+# https://github.com/hvac/hvac/issues/797
+@pytest.fixture
+def empty_response(requests_unparseable_response):
+ r = requests_unparseable_response
+ r.status_code = 204
+ r._content = b""
+ r.content = b""
+ r.text = ""
return r
diff --git a/ansible_collections/community/hashi_vault/tests/unit/constraints.txt b/ansible_collections/community/hashi_vault/tests/unit/constraints.txt
new file mode 100644
index 000000000..f347921c3
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/constraints.txt
@@ -0,0 +1,64 @@
+coverage >= 4.2, < 5.0.0, != 4.3.2 ; python_version <= '3.7' # features in 4.2+ required, avoid known bug in 4.3.2 on python 2.6, coverage 5.0+ incompatible
+coverage >= 4.5.4, < 5.0.0 ; python_version > '3.7' # coverage had a bug in < 4.5.4 that would cause unit tests to hang in Python 3.8, coverage 5.0+ incompatible
+cryptography < 2.2 ; python_version < '2.7' # cryptography 2.2 drops support for python 2.6
+deepdiff < 4.0.0 ; python_version < '3' # deepdiff 4.0.0 and later require python 3
+jinja2 < 2.11 ; python_version < '2.7' # jinja2 2.11 and later require python 2.7 or later
+urllib3 < 1.24 ; python_version < '2.7' # urllib3 1.24 and later require python 2.7 or later
+pywinrm >= 0.3.0 # message encryption support
+sphinx < 1.6 ; python_version < '2.7' # sphinx 1.6 and later require python 2.7 or later
+sphinx < 1.8 ; python_version >= '2.7' # sphinx 1.8 and later are currently incompatible with rstcheck 3.3
+pygments >= 2.4.0 # Pygments 2.4.0 includes bugfixes for YAML and YAML+Jinja lexers
+wheel < 0.30.0 ; python_version < '2.7' # wheel 0.30.0 and later require python 2.7 or later
+yamllint != 1.8.0, < 1.14.0 ; python_version < '2.7' # yamllint 1.8.0 and 1.14.0+ require python 2.7+
+pycrypto >= 2.6 # Need features found in 2.6 and greater
+ncclient >= 0.5.2 # Need features added in 0.5.2 and greater
+idna < 2.6, >= 2.5 # linode requires idna < 2.9, >= 2.5, requests requires idna < 2.6, but cryptography will cause the latest version to be installed instead
+paramiko < 2.4.0 ; python_version < '2.7' # paramiko 2.4.0 drops support for python 2.6
+pytest < 3.3.0 ; python_version < '2.7' # pytest 3.3.0 drops support for python 2.6
+pytest < 5.0.0 ; python_version == '2.7' # pytest 5.0.0 and later will no longer support python 2.7
+pytest-forked < 1.0.2 ; python_version < '2.7' # pytest-forked 1.0.2 and later require python 2.7 or later
+pytest-forked >= 1.0.2 ; python_version >= '2.7' # pytest-forked before 1.0.2 does not work with pytest 4.2.0+ (which requires python 2.7+)
+ntlm-auth >= 1.3.0 # message encryption support using cryptography
+requests < 2.20.0 ; python_version < '2.7' # requests 2.20.0 drops support for python 2.6
+requests-ntlm >= 1.1.0 # message encryption support
+requests-credssp >= 0.1.0 # message encryption support
+voluptuous >= 0.11.0 # Schema recursion via Self
+openshift >= 0.6.2, < 0.9.0 # merge_type support
+virtualenv < 16.0.0 ; python_version < '2.7' # virtualenv 16.0.0 and later require python 2.7 or later
+pathspec < 0.6.0 ; python_version < '2.7' # pathspec 0.6.0 and later require python 2.7 or later
+pyopenssl < 18.0.0 ; python_version < '2.7' # pyOpenSSL 18.0.0 and later require python 2.7 or later
+pyfmg == 0.6.1 # newer versions do not pass current unit tests
+pyyaml < 5.1 ; python_version < '2.7' # pyyaml 5.1 and later require python 2.7 or later
+pycparser < 2.19 ; python_version < '2.7' # pycparser 2.19 and later require python 2.7 or later
+mock >= 2.0.0 # needed for features backported from Python 3.6 unittest.mock (assert_called, assert_called_once...)
+pytest-mock >= 1.4.0 # needed for mock_use_standalone_module pytest option
+xmltodict < 0.12.0 ; python_version < '2.7' # xmltodict 0.12.0 and later require python 2.7 or later
+lxml < 4.3.0 ; python_version < '2.7' # lxml 4.3.0 and later require python 2.7 or later
+pyvmomi < 6.0.0 ; python_version < '2.7' # pyvmomi 6.0.0 and later require python 2.7 or later
+pyone == 1.1.9 # newer versions do not pass current integration tests
+boto3 < 1.11 ; python_version < '2.7' # boto3 1.11 drops Python 2.6 support
+botocore >= 1.10.0, < 1.14 ; python_version < '2.7' # adds support for the following AWS services: secretsmanager, fms, and acm-pca; botocore 1.14 drops Python 2.6 support
+botocore >= 1.10.0 ; python_version >= '2.7' # adds support for the following AWS services: secretsmanager, fms, and acm-pca
+setuptools < 45 ; python_version <= '2.7' # setuptools 45 and later require python 3.5 or later
+cffi >= 1.14.2, != 1.14.3 # Yanked version which older versions of pip will still install:
+
+# freeze pylint and its requirements for consistent test results
+astroid == 2.2.5
+isort == 4.3.15
+lazy-object-proxy == 1.3.1
+mccabe == 0.6.1
+pylint == 2.3.1
+typed-ast == 1.4.0 # 1.4.0 is required to compile on Python 3.8
+wrapt == 1.11.1
+
+# hvac
+hvac >= 1.2.1 ; python_version >= '3.6'
+
+# urllib3
+# these should be satisfied naturally by the requests versions required by hvac anyway
+urllib3 >= 1.15 ; python_version >= '3.6' # we need raise_on_status for retry support to raise the correct exceptions https://github.com/urllib3/urllib3/blob/main/CHANGES.rst#115-2016-04-06
+
+# requests
+# https://github.com/psf/requests/pull/6356
+requests >= 2.29 ; python_version >= '3.7'
+requests < 2.28 ; python_version < '3.7'
diff --git a/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_connection_read_response.json b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_connection_read_response.json
new file mode 100644
index 000000000..36c4429a8
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_connection_read_response.json
@@ -0,0 +1,20 @@
+{
+ "auth": null,
+ "data": {
+ "allowed_roles": [],
+ "connection_details": {
+ "connection_url": "postgresql://{{username}}:{{password}}@postgres:5432/postgres?sslmode=disable",
+ "username": "UserName"
+ },
+ "password_policy": "",
+ "plugin_name": "postgresql-database-plugin",
+ "plugin_version": "",
+ "root_credentials_rotate_statements": []
+ },
+ "lease_duration": 0,
+ "lease_id": "",
+ "renewable": false,
+ "request_id": "91909ec0-cd89-489c-a7cf-2a82d2258b4d",
+ "warnings": null,
+ "wrap_info": null
+}
diff --git a/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_connections_list_response.json b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_connections_list_response.json
new file mode 100644
index 000000000..c5abad4ce
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_connections_list_response.json
@@ -0,0 +1,21 @@
+{
+ "auth": null,
+ "data": {
+ "keys": [
+ "con1",
+ "con2",
+ "con3"
+ ]
+ },
+ "connections": [
+ "con1",
+ "con2",
+ "con3"
+ ],
+ "lease_duration": 0,
+ "lease_id": "",
+ "renewable": false,
+ "request_id": "91909ec0-cd89-489c-a7cf-2a82d2258b4d",
+ "warnings": null,
+ "wrap_info": null
+}
diff --git a/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_role_read_response.json b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_role_read_response.json
new file mode 100644
index 000000000..c840ad1cb
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_role_read_response.json
@@ -0,0 +1,22 @@
+{
+ "auth": null,
+ "data": {
+ "creation_statements": [
+ "CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
+ "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"
+ ],
+ "credential_type": "password",
+ "db_name": "SomeConnection",
+ "default_ttl": 3600,
+ "max_ttl": 86400,
+ "renew_statements": [],
+ "revocation_statements": [],
+ "rollback_statements": []
+ },
+ "lease_duration": 0,
+ "lease_id": "",
+ "renewable": false,
+ "request_id": "91909ec0-cd89-489c-a7cf-2a82d2258b4d",
+ "warnings": null,
+ "wrap_info": null
+}
diff --git a/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_roles_list_response.json b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_roles_list_response.json
new file mode 100644
index 000000000..00062fa20
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_roles_list_response.json
@@ -0,0 +1,21 @@
+{
+ "auth": null,
+ "data": {
+ "keys": [
+ "dyn_role1",
+ "dyn_role2",
+ "dyn_role3"
+ ]
+ },
+ "roles": [
+ "dyn_role1",
+ "dyn_role2",
+ "dyn_role3"
+ ],
+ "lease_duration": 0,
+ "lease_id": "",
+ "renewable": false,
+ "request_id": "91909ec0-cd89-489c-a7cf-2a82d2258b4d",
+ "warnings": null,
+ "wrap_info": null
+}
diff --git a/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_role_get_credentials_response.json b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_role_get_credentials_response.json
new file mode 100644
index 000000000..34e901a12
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_role_get_credentials_response.json
@@ -0,0 +1,25 @@
+{
+ "data": {
+ "last_vault_rotation": "2024-01-01T09:00:00+01:00",
+ "password": "Th3_$3cr3t_P@ss!",
+ "rotation_period": 86400,
+ "ttl": 123456,
+ "username": "SomeUser"
+ },
+ "raw": {
+ "auth": null,
+ "data": {
+ "last_vault_rotation": "2024-01-01T09:00:00+01:00",
+ "password": "Th3_$3cr3t_P@ss!",
+ "rotation_period": 86400,
+ "ttl": 123456,
+ "username": "SomeUser"
+ },
+ "lease_duration": 0,
+ "lease_id": "",
+ "renewable": false,
+ "request_id": "91909ec0-cd89-489c-a7cf-2a82d2258b4d",
+ "warnings": null,
+ "wrap_info": null
+ }
+}
diff --git a/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_role_read_response.json b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_role_read_response.json
new file mode 100644
index 000000000..cd4cbdc63
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_role_read_response.json
@@ -0,0 +1,18 @@
+{
+ "auth": null,
+ "data": {
+ "credential_type": "password",
+ "db_name": "SomeConnection",
+ "last_vault_rotation": "2024-01-01T09:00:00 +01:00",
+ "rotation_period": 86400,
+ "rotation_statements": [
+ "ALTER USER \"{{name}}\" WITH PASSWORD '{{password}}';"
+ ]
+ },
+ "lease_duration": 0,
+ "lease_id": "",
+ "renewable": false,
+ "request_id": "91909ec0-cd89-489c-a7cf-2a82d2258b4d",
+ "warnings": null,
+ "wrap_info": null
+}
diff --git a/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_roles_list_response.json b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_roles_list_response.json
new file mode 100644
index 000000000..be8700112
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/fixtures/database_static_roles_list_response.json
@@ -0,0 +1,21 @@
+{
+ "auth": null,
+ "data": {
+ "keys": [
+ "role1",
+ "role2",
+ "role3"
+ ]
+ },
+ "roles": [
+ "role1",
+ "role2",
+ "role3"
+ ],
+ "lease_duration": 0,
+ "lease_id": "",
+ "renewable": false,
+ "request_id": "91909ec0-cd89-489c-a7cf-2a82d2258b4d",
+ "warnings": null,
+ "wrap_info": null
+}
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_token_create.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_token_create.py
index 05a74f8b4..37404d9a2 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_token_create.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_token_create.py
@@ -6,7 +6,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import sys
import pytest
from ansible.plugins.loader import lookup_loader
@@ -150,11 +149,7 @@ class TestVaultTokenCreateLookup(object):
"lookup result did not match expected result:\nlookup: %r\nexpected: %r" % (result, token_create_response)
)
- if sys.version_info < (3, 8):
- # TODO: remove when python < 3.8 is dropped
- assert pass_thru_options.items() <= client.auth.token.create.call_args[1].items()
- else:
- assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items()
+ assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items()
def test_vault_token_create_orphan_options(
self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options, orphan_option_translation, token_create_response
@@ -175,11 +170,7 @@ class TestVaultTokenCreateLookup(object):
"lookup result did not match expected result:\nlookup: %r\nexpected: %r" % (result, token_create_response)
)
- if sys.version_info < (3, 8):
- # TODO: remove when python < 3.8 is dropped
- call_kwargs = client.auth.token.create_orphan.call_args[1]
- else:
- call_kwargs = client.auth.token.create_orphan.call_args.kwargs
+ call_kwargs = client.auth.token.create_orphan.call_args.kwargs
for name, orphan in orphan_option_translation.items():
assert name not in call_kwargs, (
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_write.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_write.py
index c3c325228..eaac0ff08 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_write.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/lookup/test_vault_write.py
@@ -68,18 +68,18 @@ class TestVaultWriteLookup(object):
def test_vault_write_return_data(self, vault_write_lookup, minimal_vars, approle_secret_id_write_response, vault_client, paths, data, wrap_ttl):
client = vault_client
- expected_calls = [mock.call(path=p, wrap_ttl=wrap_ttl, **data) for p in paths]
+ expected_calls = [mock.call(path=p, wrap_ttl=wrap_ttl, data=data) for p in paths]
- def _fake_write(path, wrap_ttl, **data):
+ def _fake_write(path, wrap_ttl, data=None):
r = approle_secret_id_write_response.copy()
r.update({'path': path})
return r
- client.write = mock.Mock(wraps=_fake_write)
+ client.write_data = mock.Mock(wraps=_fake_write)
response = vault_write_lookup.run(terms=paths, variables=minimal_vars, wrap_ttl=wrap_ttl, data=data)
- client.write.assert_has_calls(expected_calls)
+ client.write_data.assert_has_calls(expected_calls)
assert len(response) == len(paths), "%i paths processed but got %i responses" % (len(paths), len(response))
@@ -96,7 +96,7 @@ class TestVaultWriteLookup(object):
requests_unparseable_response.status_code = 204
- client.write.return_value = requests_unparseable_response
+ client.write_data.return_value = requests_unparseable_response
response = vault_write_lookup.run(terms=['fake'], variables=minimal_vars)
@@ -108,7 +108,7 @@ class TestVaultWriteLookup(object):
requests_unparseable_response.status_code = 200
requests_unparseable_response.content = '﷽'
- client.write.return_value = requests_unparseable_response
+ client.write_data.return_value = requests_unparseable_response
with mock.patch('ansible_collections.community.hashi_vault.plugins.lookup.vault_write.display.warning') as warning:
response = vault_write_lookup.run(terms=['fake'], variables=minimal_vars)
@@ -127,7 +127,40 @@ class TestVaultWriteLookup(object):
def test_vault_write_exceptions(self, vault_write_lookup, minimal_vars, vault_client, exc):
client = vault_client
- client.write.side_effect = exc[0]
+ client.write_data.side_effect = exc[0]
with pytest.raises(AnsibleError, match=exc[1]):
vault_write_lookup.run(terms=['fake'], variables=minimal_vars)
+
+ @pytest.mark.parametrize(
+ 'data',
+ [
+ {"path": mock.sentinel.path_value},
+ {"wrap_ttl": mock.sentinel.wrap_ttl_value},
+ {"path": mock.sentinel.data_value, "wrap_ttl": mock.sentinel.write_ttl_value},
+ ],
+ )
+ def test_vault_write_data_fallback_bad_params(self, vault_write_lookup, minimal_vars, vault_client, data):
+ client = vault_client
+ client.mock_add_spec(['write'])
+
+ with pytest.raises(AnsibleError, match=r"To use 'path' or 'wrap_ttl' as data keys, use hvac >= 1\.2"):
+ vault_write_lookup.run(terms=['fake'], variables=minimal_vars, data=data)
+
+ client.write.assert_not_called()
+
+ @pytest.mark.parametrize(
+ 'data',
+ [
+ {"item1": mock.sentinel.item1_value},
+ {"item2": mock.sentinel.item2_value},
+ {"item1": mock.sentinel.item1_value, "item2": mock.sentinel.item2_value},
+ ],
+ )
+ def test_vault_write_data_fallback_write(self, vault_write_lookup, minimal_vars, vault_client, data):
+ client = vault_client
+ client.mock_add_spec(['write'])
+
+ vault_write_lookup.run(terms=['fake'], variables=minimal_vars, data=data)
+
+ client.write.assert_called_once_with(path='fake', wrap_ttl=None, **data)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_aws_iam.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_aws_iam.py
index 678146b92..ac867dcd6 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_aws_iam.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_aws_iam.py
@@ -163,7 +163,7 @@ class TestAuthAwsIam(object):
params = auth_aws_iam._auth_aws_iam_login_params
- assert boto3.session.Session.called_once_with(profile_name=profile)
+ boto3.session.Session.assert_called_once_with(profile_name=profile)
assert params['access_key'] == aws_access_key
assert params['secret_key'] == aws_secret_key
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_azure.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_azure.py
index 747a432df..f0aabd78a 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_azure.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_azure.py
@@ -157,10 +157,10 @@ class TestAuthAzure(object):
credential.get_token.return_value.token = jwt
auth_azure.validate()
- assert mocked_credential_class.called_once_with(
+ mocked_credential_class.assert_called_once_with(
azure_tenant_id, azure_client_id, azure_client_secret
)
- assert credential.get_token.called_once_with(
+ credential.get_token.assert_called_once_with(
'https://management.azure.com//.default'
)
@@ -194,8 +194,8 @@ class TestAuthAzure(object):
credential.get_token.return_value.token = jwt
auth_azure.validate()
- assert mocked_credential_class.called_once_with(azure_client_id)
- assert credential.get_token.called_once_with(
+ mocked_credential_class.assert_called_once_with(client_id=azure_client_id)
+ credential.get_token.assert_called_once_with(
'https://management.azure.com//.default'
)
@@ -215,8 +215,8 @@ class TestAuthAzure(object):
credential.get_token.return_value.token = jwt
auth_azure.validate()
- assert mocked_credential_class.called_once_with()
- assert credential.get_token.called_once_with(
+ mocked_credential_class.assert_called_once_with()
+ credential.get_token.assert_called_once_with(
'https://management.azure.com//.default'
)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_token.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_token.py
index d8a13435b..5003082a5 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_token.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_auth_token.py
@@ -11,11 +11,7 @@ import pytest
from ......tests.unit.compat import mock
-try:
- import hvac
-except ImportError:
- # python 2.6, which isn't supported anyway
- hvac = mock.MagicMock()
+import hvac
from ......plugins.module_utils._auth_method_token import (
HashiVaultAuthMethodToken,
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_hashi_vault_auth_method_base.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_hashi_vault_auth_method_base.py
index 828cbdefc..36dfbcec0 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_hashi_vault_auth_method_base.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/authentication/test_hashi_vault_auth_method_base.py
@@ -8,13 +8,10 @@ __metaclass__ = type
import pytest
-from ansible_collections.community.hashi_vault.tests.unit.compat import mock
-
-from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import (
+from ......plugins.module_utils._hashi_vault_common import (
HashiVaultAuthMethodBase,
HashiVaultOptionGroupBase,
HashiVaultValueError,
- _stringify,
)
@@ -75,12 +72,3 @@ class TestHashiVaultAuthMethodBase(object):
auth_base.deprecate(msg, version, date, collection_name)
deprecator.assert_called_once_with(msg, version=version, date=date, collection_name=collection_name)
-
- def test_has_stringify(self, auth_base):
- v = 'X'
- wrapper = mock.Mock(wraps=_stringify)
- with mock.patch('ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common._stringify', wrapper):
- r = auth_base._stringify(v)
-
- wrapper.assert_called_once_with(v)
- assert r == v
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/option_adapter/test_hashi_vault_option_adapter.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/option_adapter/test_hashi_vault_option_adapter.py
index d3e205d68..9226de003 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/option_adapter/test_hashi_vault_option_adapter.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/option_adapter/test_hashi_vault_option_adapter.py
@@ -8,7 +8,7 @@ __metaclass__ = type
import pytest
-from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultOptionAdapter
+from ......plugins.module_utils._hashi_vault_common import HashiVaultOptionAdapter
SAMPLE_DICT = {
@@ -103,18 +103,32 @@ class TestHashiVaultOptionAdapter(object):
def test_has_option(self, adapter, option, expected):
assert adapter.has_option(option) == expected
- @pytest.mark.parametrize('value', ['__VALUE'])
- @pytest.mark.parametrize('option', (SAMPLE_KEYS + MISSING_KEYS))
- def test_set_option(self, adapter, option, value, sample_dict):
+ @pytest.mark.parametrize('option', SAMPLE_KEYS)
+ def test_set_option_existing(self, adapter, option, sample_dict):
+ value = type(sample_dict.get(option, ""))()
adapter.set_option(option, value)
-
# first check the underlying data, then ensure the adapter refelcts the change too
assert sample_dict[option] == value
assert adapter.get_option(option) == value
+ @pytest.mark.parametrize('option', MISSING_KEYS)
+ def test_set_option_missing(self, request, adapter, option, sample_dict):
+ value = MARKER
+
+ for mark in request.node.own_markers:
+ if mark.name == 'option_adapter_raise_on_missing':
+ from ansible.errors import AnsibleError
+ with pytest.raises(AnsibleError, match=rf"^Requested entry.*?setting: {option}.*?was not defined in configuration"):
+ adapter.set_option(option, value)
+ break
+ else:
+ adapter.set_option(option, value)
+ assert sample_dict[option] == value
+ assert adapter.get_option(option) == value
+
@pytest.mark.parametrize('default', [MARKER])
- @pytest.mark.parametrize('option,expected', [(o, SAMPLE_DICT[o]) for o in SAMPLE_KEYS] + [(o, MARKER) for o in MISSING_KEYS])
- def test_set_option_default(self, adapter, option, default, expected, sample_dict):
+ @pytest.mark.parametrize('option,expected', [(o, SAMPLE_DICT[o]) for o in SAMPLE_KEYS])
+ def test_set_option_default_existing(self, adapter, option, default, expected, sample_dict):
value = adapter.set_option_default(option, default)
# check return data, underlying data structure, and adapter retrieval
@@ -122,25 +136,47 @@ class TestHashiVaultOptionAdapter(object):
assert sample_dict[option] == expected
assert adapter.get_option(option) == expected
- @pytest.mark.parametrize('options', [[SAMPLE_KEYS[0], MISSING_KEYS[0]]])
- def test_set_options(self, adapter, options, sample_dict):
- update = dict([(o, '__VALUE_%i' % i) for i, o in enumerate(options)])
+ @pytest.mark.parametrize('default', [MARKER])
+ @pytest.mark.parametrize('option,expected', [(o, MARKER) for o in MISSING_KEYS])
+ def test_set_option_default_missing(self, request, adapter, option, default, expected, sample_dict):
+ for mark in request.node.own_markers:
+ if mark.name == 'option_adapter_raise_on_missing':
+ from ansible.errors import AnsibleError
+ with pytest.raises(AnsibleError, match=rf"^Requested entry.*?setting: {option}.*?was not defined in configuration"):
+ adapter.set_option_default(option, default)
+ break
+ else:
+ value = adapter.set_option_default(option, default)
+ # check return data, underlying data structure, and adapter retrieval
+ assert value == expected
+ assert sample_dict[option] == expected
+ assert adapter.get_option(option) == expected
- adapter.set_options(**update)
+ @pytest.mark.parametrize('options', [[SAMPLE_KEYS[0], MISSING_KEYS[0]]])
+ def test_set_options(self, request, adapter, options, sample_dict):
+ update = dict([(o, type(sample_dict.get(o, ""))(i)) for i, o in enumerate(options)])
+
+ for mark in request.node.own_markers:
+ if mark.name == 'option_adapter_raise_on_missing':
+ from ansible.errors import AnsibleError
+ with pytest.raises(AnsibleError, match=r"^Requested entry.*?setting:.*?was not defined in configuration"):
+ adapter.set_options(**update)
+ break
+ else:
+ adapter.set_options(**update)
+ for k in MISSING_KEYS:
+ if k in update:
+ assert sample_dict[k] == update[k]
+ assert adapter.get_option(k) == update[k]
+ else:
+ assert k not in sample_dict
+ assert not adapter.has_option(k)
for k in SAMPLE_KEYS:
expected = update[k] if k in update else SAMPLE_DICT[k]
assert sample_dict[k] == expected
assert adapter.get_option(k) == expected
- for k in MISSING_KEYS:
- if k in update:
- assert sample_dict[k] == update[k]
- assert adapter.get_option(k) == update[k]
- else:
- assert k not in sample_dict
- assert not adapter.has_option(k)
-
@pytest.mark.parametrize('options', [[SAMPLE_KEYS[0], MISSING_KEYS[0]]])
def test_get_options_mixed(self, adapter, options):
with pytest.raises(KeyError):
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_connection_options.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_connection_options.py
index 8766388e9..6a45b9859 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_connection_options.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_connection_options.py
@@ -101,7 +101,7 @@ class TestHashiVaultConnectionOptions(object):
with mock.patch.dict(os.environ, envpatch):
connection_options._boolean_or_cacert()
- assert predefined_options['ca_cert'] == expected
+ assert connection_options._conopt_verify == expected
# _process_option_proxies
# proxies can be specified as a dictionary where key is protocol/scheme
@@ -248,7 +248,7 @@ class TestHashiVaultConnectionOptions(object):
# these should always be returned
assert 'url' in opts and opts['url'] == predefined_options['url']
- assert 'verify' in opts and opts['verify'] == predefined_options['ca_cert']
+ assert 'verify' in opts and opts['verify'] == connection_options._conopt_verify
# these are optional
assert 'proxies' not in opts or opts['proxies'] == predefined_options['proxies']
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_helper.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_helper.py
index 6a5c6002b..14db5d1c8 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_helper.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/module_utils/test_hashi_vault_helper.py
@@ -12,7 +12,6 @@ import pytest
from .....tests.unit.compat import mock
from .....plugins.module_utils._hashi_vault_common import (
HashiVaultHelper,
- _stringify,
)
@@ -48,12 +47,3 @@ class TestHashiVaultHelper(object):
client = hashi_vault_helper.get_vault_client(hashi_vault_logout_inferred_token=True)
assert client.token is None
-
- def test_has_stringify(self, hashi_vault_helper):
- v = 'X'
- wrapper = mock.Mock(wraps=_stringify)
- with mock.patch('ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common._stringify', wrapper):
- r = hashi_vault_helper._stringify(v)
-
- wrapper.assert_called_once_with(v)
- assert r == v, '%r != %r' % (r, v)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_configure.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_configure.py
new file mode 100644
index 000000000..17a4e063e
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_configure.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_connection_configure
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_connection():
+ return {
+ "connection_name": "foo",
+ "plugin_name": "bar",
+ "allowed_roles": [
+ "baz",
+ ],
+ "connection_url": "postgresql://{{'{{username}}'}}:{{'{{password}}'}}@postgres:5432/postgres?sslmode=disable",
+ "connection_username": "SomeUser",
+ "connection_password": "SomePass",
+ }
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_connection())
+ opt.update(kwargs)
+ return opt
+
+
+class TestModuleVaultDatabaseConnectionConfigure:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connection_configure_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_configure.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connection_configure_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_configure.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connection_configure_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_connection_configure,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_configure.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/config/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_connection_configure_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.configure.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_configure.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connection_configure_success(
+ self, patch_ansible_module, empty_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.configure.return_value = empty_response
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_configure.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.configure.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["connection_name"],
+ plugin_name=patch_ansible_module["plugin_name"],
+ allowed_roles=patch_ansible_module["allowed_roles"],
+ connection_url=patch_ansible_module["connection_url"],
+ username=patch_ansible_module["connection_username"],
+ password=patch_ansible_module["connection_password"],
+ )
+
+ assert result["changed"] is True
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_delete.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_delete.py
new file mode 100644
index 000000000..5a24f85fa
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_delete.py
@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_connection_delete
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_connection():
+ return {"connection_name": "foo"}
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_connection())
+ opt.update(kwargs)
+ return opt
+
+
+class TestModuleVaultDatabaseConnectionDelete:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connection_delete_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connection_delete_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connection_delete_success(
+ self, patch_ansible_module, empty_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.delete_connection.return_value = empty_response
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.delete_connection.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["connection_name"],
+ )
+
+ assert result["changed"] is True
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connection_delete_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_connection_delete,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/config/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_connection_delete_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.delete_connection.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_read.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_read.py
new file mode 100644
index 000000000..062ff7689
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_read.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_connection_read
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_connection():
+ return {"connection_name": "foo"}
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_connection())
+ opt.update(kwargs)
+ return opt
+
+
+@pytest.fixture
+def list_response(fixture_loader):
+ return fixture_loader("database_connection_read_response.json")
+
+
+class TestModuleVaultDatabaseConnectionRead:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connection_read_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connection_read_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connection_read_return_data(
+ self, patch_ansible_module, list_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.read_connection.return_value = list_response.copy()
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.read_connection.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["connection_name"],
+ )
+
+ raw = list_response.copy()
+ data = raw["data"]
+
+ assert (
+ result["raw"] == raw
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["data"] == data
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connection_read_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_connection_read,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/config/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_connection_read_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.read_connection.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_reset.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_reset.py
new file mode 100644
index 000000000..bddb858fd
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connection_reset.py
@@ -0,0 +1,181 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_connection_reset
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_connection():
+ return {"connection_name": "foo"}
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_connection())
+ opt.update(kwargs)
+ return opt
+
+
+class TestModuleVaultDatabaseConnectionReset:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connection_reset_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_reset.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connection_reset_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_reset.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connection_reset_success(
+ self, patch_ansible_module, empty_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.reset_connection.return_value = empty_response
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_reset.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.reset_connection.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["connection_name"],
+ )
+
+ assert result["changed"] is True
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connection_reset_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_connection_reset,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_reset.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/reset/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_connection_reset_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.reset_connection.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connection_reset.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connections_list.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connections_list.py
new file mode 100644
index 000000000..057adc6f6
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_connections_list.py
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Martin Chmielewski (@M4rt1nCh)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_connections_list
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(kwargs)
+ return opt
+
+
+@pytest.fixture
+def list_response(fixture_loader):
+ return fixture_loader("database_connections_list_response.json")
+
+
+class TestModuleVaultDatabaseConnectionsList:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connections_list_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connections_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_connections_list_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connections_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connections_list_return_data(
+ self, patch_ansible_module, list_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.list_connections.return_value = list_response.copy()
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connections_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.list_connections.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"]
+ )
+
+ raw = list_response.copy()
+ data = raw["data"]
+ roles = data["keys"]
+
+ assert (
+ result["raw"] == raw
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["data"] == data
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["connections"] == roles
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connections_list_no_data(
+ self, patch_ansible_module, vault_client, capfd
+ ):
+ client = vault_client
+ raw = {"errors": []}
+ client.secrets.database.list_connections.return_value = raw
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connections_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.list_connections.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"]
+ )
+
+ empty_data = {"keys": []}
+ assert result["raw"] == raw
+ assert result["data"] == empty_data
+ assert result["connections"] == empty_data["keys"]
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_connections_list_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_connections_list,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_connections_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/config'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_connections_list_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.list_connections.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_connections_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_create.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_create.py
new file mode 100644
index 000000000..f8c752a00
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_create.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_role_create
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_role():
+ return {
+ "role_name": "foo",
+ "connection_name": "bar",
+ "creation_statements": [
+ "CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
+ 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";',
+ ],
+ "default_ttl": 3600,
+ "max_ttl": 86400,
+ }
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_role())
+ opt.update(kwargs)
+ return opt
+
+
+class TestModuleVaultDatabaseRoleCreate:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_role_create_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_role_create_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_role_create_success(
+ self, patch_ansible_module, empty_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.create_role.return_value = empty_response
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.create_role.assert_called_once_with(
+ name=patch_ansible_module["role_name"],
+ db_name=patch_ansible_module["connection_name"],
+ creation_statements=patch_ansible_module["creation_statements"],
+ revocation_statements=patch_ansible_module.get("revocation_statements"),
+ rollback_statements=patch_ansible_module.get("rollback_statements"),
+ renew_statements=patch_ansible_module.get("renew_statements"),
+ default_ttl=patch_ansible_module["default_ttl"],
+ max_ttl=patch_ansible_module["max_ttl"],
+ mount_point=patch_ansible_module["engine_mount_point"],
+ )
+
+ assert result["changed"] is True
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_role_create_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_role_create,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/roles/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_role_create_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.create_role.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_delete.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_delete.py
new file mode 100644
index 000000000..e0b254bd1
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_delete.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_role_delete
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_role():
+ return {"role_name": "foo"}
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_role())
+ opt.update(kwargs)
+ return opt
+
+
+class TestModuleVaultDatabaseRoleDelete:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_role_delete_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_role_delete_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_role_delete_success(
+ self, patch_ansible_module, empty_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.delete_role.return_value = empty_response
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.delete_role.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["role_name"],
+ )
+
+ assert result["changed"] is True
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_role_delete_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_role_delete,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/roles/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_role_delete_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.delete_role.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_delete.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_read.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_read.py
new file mode 100644
index 000000000..9dfcd4117
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_role_read.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_role_read
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_role():
+ return {"role_name": "foo"}
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_role())
+ opt.update(kwargs)
+ return opt
+
+
+@pytest.fixture
+def list_response(fixture_loader):
+ return fixture_loader("database_role_read_response.json")
+
+
+class TestModuleVaultDatabaseRoleRead:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_role_read_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_role_read_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_role_read_return_data(
+ self, patch_ansible_module, list_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.read_role.return_value = list_response.copy()
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.read_role.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["role_name"],
+ )
+
+ raw = list_response.copy()
+ data = raw["data"]
+
+ assert (
+ result["raw"] == raw
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["data"] == data
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_role_read_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_role_read,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/roles/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_role_read_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.read_role.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_roles_list.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_roles_list.py
new file mode 100644
index 000000000..bcfadb062
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_roles_list.py
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Martin Chmielewski (@M4rt1nCh)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_roles_list
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(kwargs)
+ return opt
+
+
+@pytest.fixture
+def list_response(fixture_loader):
+ return fixture_loader("database_roles_list_response.json")
+
+
+class TestModuleVaultDatabaseRolesList:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_roles_list_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_roles_list_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_roles_list_return_data(
+ self, patch_ansible_module, list_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.list_roles.return_value = list_response.copy()
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.list_roles.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"]
+ )
+
+ raw = list_response.copy()
+ data = raw["data"]
+ roles = data["keys"]
+
+ assert (
+ result["raw"] == raw
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["data"] == data
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["roles"] == roles
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_roles_list_no_data(
+ self, patch_ansible_module, vault_client, capfd
+ ):
+ client = vault_client
+ raw = {"errors": []}
+ client.secrets.database.list_roles.return_value = raw
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.list_roles.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"]
+ )
+
+ empty_data = {"keys": []}
+ assert result["raw"] == raw
+ assert result["data"] == empty_data
+ assert result["roles"] == empty_data["keys"]
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_roles_list_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_roles_list,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/roles'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_roles_list_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.list_roles.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_rotate_root_credentials.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_rotate_root_credentials.py
new file mode 100644
index 000000000..1b22aaa0c
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_rotate_root_credentials.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_rotate_root_credentials
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_connection():
+ return {"connection_name": "foo"}
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_connection())
+ opt.update(kwargs)
+ return opt
+
+
+class TestModuleVaultDatabaseRotateRootCredentials:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_rotate_root_credentials_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_rotate_root_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_rotate_root_credentials_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_rotate_root_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_rotate_root_credentials_success(
+ self, patch_ansible_module, empty_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.rotate_root_credentials.return_value = empty_response
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_rotate_root_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.rotate_root_credentials.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["connection_name"],
+ )
+
+ assert result["changed"] is True
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_rotate_root_credentials_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_rotate_root_credentials,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_rotate_root_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_rotate_root_credentials_old_hvac(self, vault_client, capfd):
+ client = vault_client
+ client.secrets.database.rotate_root_credentials.side_effect = AttributeError
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_rotate_root_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "hvac>=2.0.0 is required"
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/rotate-root/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_rotate_root_credentials_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.rotate_root_credentials.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_rotate_root_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_create.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_create.py
new file mode 100644
index 000000000..eab22477b
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_create.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_static_role_create
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_role():
+ return {
+ "role_name": "foo",
+ "connection_name": "bar",
+ "db_username": "baz",
+ "rotation_statements": [
+ "ALTER USER \"{{name}}\" WITH PASSWORD '{{password}}';"
+ ],
+ "rotation_period": 86400,
+ }
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_role())
+ opt.update(kwargs)
+ return opt
+
+
+class TestModuleVaultDatabaseStaticRoleCreate:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_role_create_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_role_create_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_role_create_success(
+ self, patch_ansible_module, empty_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.create_static_role.return_value = empty_response
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.create_static_role.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["role_name"],
+ db_name=patch_ansible_module["connection_name"],
+ username=patch_ansible_module["db_username"],
+ rotation_statements=patch_ansible_module["rotation_statements"],
+ rotation_period=patch_ansible_module["rotation_period"],
+ )
+
+ assert result["changed"] is True
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_role_create_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_static_role_create,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/static-roles/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_static_role_create_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.create_static_role.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_create.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_get_credentials.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_get_credentials.py
new file mode 100644
index 000000000..b15364ab6
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_get_credentials.py
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_static_role_get_credentials
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_role():
+ return {"role_name": "foo"}
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_role())
+ opt.update(kwargs)
+ return opt
+
+
+@pytest.fixture
+def list_response(fixture_loader):
+ return fixture_loader("database_static_role_get_credentials_response.json")
+
+
+class TestModuleVaultDatabaseStaticRoleGetCredentials:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_role_get_credentials_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_get_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_role_get_credentials_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_get_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_role_get_credentials_return_data(
+ self, patch_ansible_module, list_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.get_static_credentials.return_value = (
+ list_response.copy()
+ )
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_get_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.get_static_credentials.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["role_name"],
+ )
+
+ raw = list_response.copy()
+ data = raw["data"]
+
+ assert (
+ result["raw"] == raw
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["data"] == data
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_role_get_credentials_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_static_role_get_credentials,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_get_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/static-creds/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_static_role_get_credentials_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.get_static_credentials.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_get_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_read.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_read.py
new file mode 100644
index 000000000..6a0b087b4
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_read.py
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_static_role_read
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_role():
+ return {"role_name": "foo"}
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_role())
+ opt.update(kwargs)
+ return opt
+
+
+@pytest.fixture
+def list_response(fixture_loader):
+ return fixture_loader("database_static_role_read_response.json")
+
+
+class TestModuleVaultDatabaseStaticRoleRead:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_role_read_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_role_read_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_role_read_return_data(
+ self, patch_ansible_module, list_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.read_static_role.return_value = list_response.copy()
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.read_static_role.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["role_name"],
+ )
+
+ raw = list_response.copy()
+ data = raw["data"]
+
+ assert (
+ result["raw"] == raw
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["data"] == data
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_role_read_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_static_role_read,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/static-roles/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_static_role_read_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.read_static_role.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_read.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_rotate_credentials.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_rotate_credentials.py
new file mode 100644
index 000000000..b6e266755
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_role_rotate_credentials.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_static_role_rotate_credentials
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _sample_role():
+ return {"role_name": "foo"}
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(_sample_role())
+ opt.update(kwargs)
+ return opt
+
+
+class TestModuleVaultDatabaseStaticRoleRotateCredentials:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_role_rotate_credentials_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_rotate_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_role_rotate_credentials_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_rotate_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_role_rotate_credentials_success(
+ self, patch_ansible_module, empty_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.rotate_static_role_credentials.return_value = empty_response
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_rotate_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.rotate_static_role_credentials.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"],
+ name=patch_ansible_module["role_name"],
+ )
+
+ assert result["changed"] is True
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_role_rotate_credentials_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_static_role_rotate_credentials,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_rotate_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_role_rotate_credentials_old_hvac(self, vault_client, capfd):
+ client = vault_client
+ client.secrets.database.rotate_static_role_credentials.side_effect = AttributeError
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_rotate_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "hvac>=2.0.0 is required"
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/rotate-role/([^']+)'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_static_role_rotate_credentials_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.rotate_static_role_credentials.side_effect = exc[0](
+ exc[1]
+ )
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_role_rotate_credentials.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_roles_list.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_roles_list.py
new file mode 100644
index 000000000..7a028e76d
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_database_static_roles_list.py
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024 Brian Scholer (@briantist)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import pytest
+import re
+import json
+
+from ansible.module_utils.basic import missing_required_lib
+
+from ...compat import mock
+from .....plugins.modules import vault_database_static_roles_list
+from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+hvac = pytest.importorskip("hvac")
+
+
+pytestmark = pytest.mark.usefixtures(
+ "patch_ansible_module",
+ "patch_authenticator",
+ "patch_get_vault_client",
+)
+
+
+def _connection_options():
+ return {
+ "auth_method": "token",
+ "url": "http://myvault",
+ "token": "beep-boop",
+ }
+
+
+def _sample_options():
+ return {
+ "engine_mount_point": "dbmount",
+ }
+
+
+def _combined_options(**kwargs):
+ opt = _connection_options()
+ opt.update(_sample_options())
+ opt.update(kwargs)
+ return opt
+
+
+@pytest.fixture
+def list_response(fixture_loader):
+ return fixture_loader("database_static_roles_list_response.json")
+
+
+class TestModuleVaultDatabaseStaticRolesList:
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_roles_list_authentication_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.authenticate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg", "result: %r" % result
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ @pytest.mark.parametrize(
+ "exc",
+ [HashiVaultValueError("throwaway msg"), NotImplementedError("throwaway msg")],
+ )
+ def test_vault_database_static_roles_list_auth_validation_error(
+ self, authenticator, exc, capfd
+ ):
+ authenticator.validate.side_effect = exc
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == "throwaway msg"
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_roles_list_return_data(
+ self, patch_ansible_module, list_response, vault_client, capfd
+ ):
+ client = vault_client
+ client.secrets.database.list_static_roles.return_value = list_response.copy()
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.list_static_roles.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"]
+ )
+
+ raw = list_response.copy()
+ data = raw["data"]
+ roles = data["keys"]
+
+ assert (
+ result["raw"] == raw
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["data"] == data
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+ assert (
+ result["roles"] == roles
+ ), "module result did not match expected result:\nexpected: %r\ngot: %r" % (
+ list_response,
+ result,
+ )
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_roles_list_no_data(
+ self, patch_ansible_module, vault_client, capfd
+ ):
+ client = vault_client
+ raw = {"errors": []}
+ client.secrets.database.list_static_roles.return_value = raw
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.secrets.database.list_static_roles.assert_called_once_with(
+ mount_point=patch_ansible_module["engine_mount_point"]
+ )
+
+ empty_data = {"keys": []}
+ assert result["raw"] == raw
+ assert result["data"] == empty_data
+ assert result["roles"] == empty_data["keys"]
+
+ @pytest.mark.parametrize(
+ "patch_ansible_module", [_combined_options()], indirect=True
+ )
+ def test_vault_database_static_roles_list_no_hvac(self, capfd):
+ with mock.patch.multiple(
+ vault_database_static_roles_list,
+ HAS_HVAC=False,
+ HVAC_IMPORT_ERROR=None,
+ create=True,
+ ):
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert result["msg"] == missing_required_lib("hvac")
+
+ @pytest.mark.parametrize(
+ "exc",
+ [
+ (
+ hvac.exceptions.Forbidden,
+ "",
+ r"^Forbidden: Permission Denied to path \['([^']+)'\]",
+ ),
+ (
+ hvac.exceptions.InvalidPath,
+ "",
+ r"^Invalid or missing path \['([^']+)/static-roles'\]",
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "patch_ansible_module",
+ [[_combined_options(), "engine_mount_point"]],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("opt_engine_mount_point", ["path/1", "second/path"])
+ def test_vault_database_static_roles_list_vault_exception(
+ self, vault_client, exc, opt_engine_mount_point, capfd
+ ):
+
+ client = vault_client
+ client.secrets.database.list_static_roles.side_effect = exc[0](exc[1])
+
+ with pytest.raises(SystemExit) as e:
+ vault_database_static_roles_list.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ match = re.search(exc[2], result["msg"])
+ assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2])
+
+ assert opt_engine_mount_point == match.group(1)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_pki_generate_certificate.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_pki_generate_certificate.py
index 29bba2165..c5374b537 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_pki_generate_certificate.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_pki_generate_certificate.py
@@ -71,7 +71,7 @@ def translated_options(sample_options):
if k in toplevel:
opt[toplevel[k]] = v
else:
- if type(v) is list:
+ if isinstance(v, list):
val = ','.join(v)
else:
val = v
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_write.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_write.py
index afe877e8d..59d2e38a1 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_write.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/modules/test_vault_write.py
@@ -88,7 +88,7 @@ class TestModuleVaultWrite():
@pytest.mark.parametrize('patch_ansible_module', [[_combined_options(), 'data', 'wrap_ttl']], indirect=True)
def test_vault_write_return_data(self, patch_ansible_module, approle_secret_id_write_response, vault_client, opt_wrap_ttl, opt_data, capfd):
client = vault_client
- client.write.return_value = approle_secret_id_write_response
+ client.write_data.return_value = approle_secret_id_write_response
with pytest.raises(SystemExit) as e:
vault_write.main()
@@ -98,7 +98,7 @@ class TestModuleVaultWrite():
assert e.value.code == 0, "result: %r" % (result,)
- client.write.assert_called_once_with(path=patch_ansible_module['path'], wrap_ttl=opt_wrap_ttl, **opt_data)
+ client.write_data.assert_called_once_with(path=patch_ansible_module['path'], wrap_ttl=opt_wrap_ttl, data=opt_data)
assert result['data'] == approle_secret_id_write_response, (
"module result did not match expected result:\nmodule: %r\nexpected: %r" % (result['data'], approle_secret_id_write_response)
@@ -110,7 +110,7 @@ class TestModuleVaultWrite():
requests_unparseable_response.status_code = 204
- client.write.return_value = requests_unparseable_response
+ client.write_data.return_value = requests_unparseable_response
with pytest.raises(SystemExit) as e:
vault_write.main()
@@ -129,7 +129,7 @@ class TestModuleVaultWrite():
requests_unparseable_response.status_code = 200
requests_unparseable_response.content = '﷽'
- client.write.return_value = requests_unparseable_response
+ client.write_data.return_value = requests_unparseable_response
with pytest.raises(SystemExit) as e:
vault_write.main()
@@ -166,7 +166,7 @@ class TestModuleVaultWrite():
def test_vault_write_vault_exception(self, vault_client, exc, capfd):
client = vault_client
- client.write.side_effect = exc[0]
+ client.write_data.side_effect = exc[0]
with pytest.raises(SystemExit) as e:
vault_write.main()
@@ -176,3 +176,51 @@ class TestModuleVaultWrite():
assert e.value.code != 0, "result: %r" % (result,)
assert re.search(exc[1], result['msg']) is not None
+
+ @pytest.mark.parametrize(
+ 'opt_data',
+ [
+ {"path": 'path_value'},
+ {"wrap_ttl": 'wrap_ttl_value'},
+ {"path": 'data_value', "wrap_ttl": 'write_ttl_value'},
+ ],
+ )
+ @pytest.mark.parametrize('patch_ansible_module', [[_combined_options(), 'data']], indirect=True)
+ def test_vault_write_data_fallback_bad_params(self, vault_client, opt_data, capfd):
+ client = vault_client
+ client.mock_add_spec(['write'])
+
+ with pytest.raises(SystemExit) as e:
+ vault_write.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code != 0, "result: %r" % (result,)
+ assert re.search(r"To use 'path' or 'wrap_ttl' as data keys, use hvac >= 1\.2", result['msg']) is not None
+
+ client.write.assert_not_called()
+
+ @pytest.mark.parametrize(
+ 'opt_data',
+ [
+ {"item1": 'item1_value'},
+ {"item2": 'item2_value'},
+ {"item1": 'item1_value', "item2": 'item2_value'},
+ ],
+ )
+ @pytest.mark.parametrize('patch_ansible_module', [[_combined_options(), 'data']], indirect=True)
+ def test_vault_write_data_fallback_write(self, vault_client, opt_data, patch_ansible_module, approle_secret_id_write_response, capfd):
+ client = vault_client
+ client.mock_add_spec(['write'])
+ client.write.return_value = approle_secret_id_write_response
+
+ with pytest.raises(SystemExit) as e:
+ vault_write.main()
+
+ out, err = capfd.readouterr()
+ result = json.loads(out)
+
+ assert e.value.code == 0, "result: %r" % (result,)
+
+ client.write.assert_called_once_with(path=patch_ansible_module['path'], wrap_ttl=None, **opt_data)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/authentication/conftest.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/authentication/conftest.py
deleted file mode 100644
index bf577a2d0..000000000
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/authentication/conftest.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2022 Brian Scholer (@briantist)
-# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-# pylint: disable=wildcard-import,unused-wildcard-import
-from ...module_utils.authentication.conftest import *
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/authentication/test_auth_token.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/authentication/test_auth_token.py
deleted file mode 100644
index 3dbc4c7fc..000000000
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/authentication/test_auth_token.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2022 Brian Scholer (@briantist)
-# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-import pytest
-
-from ansible.utils.unsafe_proxy import AnsibleUnsafe, AnsibleUnsafeBytes, AnsibleUnsafeText
-
-from ansible_collections.community.hashi_vault.tests.unit.compat import mock
-
-from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_token import (
- HashiVaultAuthMethodToken,
-)
-
-
-@pytest.fixture
-def option_dict():
- return {
- 'auth_method': 'fake',
- 'token': None,
- 'token_path': None,
- 'token_file': '.vault-token',
- 'token_validate': True,
- }
-
-
-@pytest.fixture(params=[AnsibleUnsafeBytes(b'ub_opaque'), AnsibleUnsafeText(u'ut_opaque'), b'b_opaque', u't_opaque'])
-def stringy(request):
- return request.param
-
-
-@pytest.fixture
-def auth_token(adapter, warner, deprecator):
- return HashiVaultAuthMethodToken(adapter, warner, deprecator)
-
-
-class TestAuthToken(object):
- def test_auth_token_unsafes(self, auth_token, client, adapter, stringy):
- adapter.set_option('token', stringy)
- adapter.set_option('token_validate', False)
-
- wrapper = mock.Mock(wraps=auth_token._stringify)
-
- with mock.patch.object(auth_token, '_stringify', wrapper):
- response = auth_token.authenticate(client, use_token=True, lookup_self=False)
-
- assert isinstance(response['auth']['client_token'], (bytes, type(u''))), repr(response['auth']['client_token'])
- assert isinstance(client.token, (bytes, type(u''))), repr(client.token)
- assert not isinstance(response['auth']['client_token'], AnsibleUnsafe), repr(response['auth']['client_token'])
- assert not isinstance(client.token, AnsibleUnsafe), repr(client.token)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/base/test_hashi_vault_lookup_base.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/base/test_hashi_vault_lookup_base.py
index 620583b41..aa71687b4 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/base/test_hashi_vault_lookup_base.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/base/test_hashi_vault_lookup_base.py
@@ -8,10 +8,11 @@ __metaclass__ = type
import pytest
-from ansible.errors import AnsibleError
+from re import escape as re_escape
+
+from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.plugins.lookup import LookupBase
-from ....compat import mock
from ......plugins.plugin_utils._hashi_vault_plugin import HashiVaultPlugin
from ......plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase
@@ -72,7 +73,6 @@ class TestHashiVaultLookupBase(object):
with pytest.raises(TypeError):
parsed = hashi_vault_lookup_module.parse_kev_term('key1=value1', first_unqualified='fake')
- # TODO: v5.0.0 - should raise not warn: https://github.com/ansible-collections/community.hashi_vault/pull/350
@pytest.mark.parametrize('term', [
'one secret=two a=1 b=2',
'a=1 secret=one b=2 secret=two',
@@ -80,10 +80,10 @@ class TestHashiVaultLookupBase(object):
])
def test_parse_kev_term_duplicate_option(self, term, hashi_vault_lookup_module):
dup_key = 'secret'
- removed_in = '5.0.0'
- expected_template = "Duplicate key '%s' in the term string '%s'.\nIn version %s of the collection, this will raise an exception."
- expected_msg = expected_template % (dup_key, term, removed_in)
+ expected_template = "Duplicate key '%s' in the term string '%s'."
+ expected_msg = expected_template % (dup_key, term)
+ expected_re = re_escape(expected_msg)
+ expected_match = "^%s$" % (expected_re,)
- with mock.patch('ansible_collections.community.hashi_vault.plugins.plugin_utils._hashi_vault_lookup_base.display') as display:
+ with pytest.raises(AnsibleOptionsError, match=expected_match):
hashi_vault_lookup_module.parse_kev_term(term, plugin_name='fake', first_unqualified=dup_key)
- display.deprecated.assert_called_once_with(expected_msg, removed_in)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/conftest.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/conftest.py
index 5c1aa7ed6..dd292cf10 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/conftest.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/conftest.py
@@ -15,18 +15,33 @@ __metaclass__ = type
import pytest
+from packaging import version
+from ansible.release import __version__ as ansible_version
from ansible.plugins import AnsiblePlugin
-from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultOptionAdapter
+from ......plugins.module_utils._hashi_vault_common import HashiVaultOptionAdapter
-class FakePlugin(AnsiblePlugin):
- _load_name = 'community.hashi_vault.fake'
+ver = version.parse(ansible_version)
+cutoff = version.parse('2.17')
+option_adapter_from_plugin_marks = pytest.mark.option_adapter_raise_on_missing if ver.release >= cutoff.release else []
+# https://github.com/ansible-collections/community.hashi_vault/issues/417
+
+
+def _generate_options(opts: dict) -> dict:
+ return {k: {"type": type(v).__name__} if v is not None else {} for k, v in opts.items()}
@pytest.fixture
def ansible_plugin(sample_dict):
- plugin = FakePlugin()
+ optdef = _generate_options(opts=sample_dict)
+
+ class LookupModule(AnsiblePlugin):
+ _load_name = 'community.hashi_vault.fake'
+
+ import ansible.constants as C
+ C.config.initialize_plugin_configuration_definitions("lookup", LookupModule._load_name, optdef)
+ plugin = LookupModule()
plugin._options = sample_dict
return plugin
@@ -39,7 +54,11 @@ def adapter_from_ansible_plugin(ansible_plugin):
return _create_adapter_from_ansible_plugin
-@pytest.fixture(params=['dict', 'dict_defaults', 'ansible_plugin'])
+@pytest.fixture(params=[
+ 'dict',
+ 'dict_defaults',
+ pytest.param('ansible_plugin', marks=option_adapter_from_plugin_marks),
+])
def adapter(request, adapter_from_dict, adapter_from_dict_defaults, adapter_from_ansible_plugin):
return {
'dict': adapter_from_dict,
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/test_hashi_vault_option_adapter.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/test_hashi_vault_option_adapter.py
index e78feec17..2d6d16ae7 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/test_hashi_vault_option_adapter.py
+++ b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/option_adapter/test_hashi_vault_option_adapter.py
@@ -12,4 +12,4 @@ __metaclass__ = type
# So we really do want to import * and so we disable lint failure on wildcard imports.
#
# pylint: disable=wildcard-import,unused-wildcard-import
-from ansible_collections.community.hashi_vault.tests.unit.plugins.module_utils.option_adapter.test_hashi_vault_option_adapter import *
+from ...module_utils.option_adapter.test_hashi_vault_option_adapter import *
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/test_hashi_vault_common_stringify.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/test_hashi_vault_common_stringify.py
deleted file mode 100644
index 1fcd3fd5e..000000000
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/test_hashi_vault_common_stringify.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2022 Brian Scholer (@briantist)
-# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-import pytest
-
-from ansible.utils.unsafe_proxy import AnsibleUnsafe, AnsibleUnsafeBytes, AnsibleUnsafeText
-
-from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import _stringify
-
-
-@pytest.fixture
-def uvalue():
- return u'fake123'
-
-
-@pytest.fixture
-def bvalue():
- return b'fake456'
-
-
-class TestHashiVaultCommonStringify(object):
- @pytest.mark.parametrize('unsafe', [True, False])
- def test_stringify_bytes(self, unsafe, bvalue):
- token = bvalue
- if unsafe:
- token = AnsibleUnsafeBytes(token)
-
- r = _stringify(token)
-
- assert isinstance(r, bytes)
- assert not isinstance(r, AnsibleUnsafe)
-
- @pytest.mark.parametrize('unsafe', [True, False])
- def test_stringify_unicode(self, unsafe, uvalue):
- token = uvalue
- utype = type(token)
- if unsafe:
- token = AnsibleUnsafeText(token)
-
- r = _stringify(token)
-
- assert isinstance(r, utype)
- assert not isinstance(r, AnsibleUnsafe)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/test_hashi_vault_helper.py b/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/test_hashi_vault_helper.py
deleted file mode 100644
index e71e625c0..000000000
--- a/ansible_collections/community/hashi_vault/tests/unit/plugins/plugin_utils/test_hashi_vault_helper.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2022 Brian Scholer (@briantist)
-# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-import pytest
-
-from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes, AnsibleUnsafeText
-
-from ansible_collections.community.hashi_vault.tests.unit.compat import mock
-from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultHelper
-
-
-@pytest.fixture
-def hashi_vault_helper():
- return HashiVaultHelper()
-
-
-@pytest.fixture
-def expected_stringify_candidates():
- return set([
- 'token',
- 'namespace',
- ])
-
-
-class TestHashiVaultHelper(object):
- def test_expected_stringify_candidates(self, hashi_vault_helper, expected_stringify_candidates):
- # If we add more candidates to the set without updating the tests,
- # this will help us catch that. The purpose is not to simply update
- # the set in the fixture, but to also add specific tests where appropriate.
- assert hashi_vault_helper.STRINGIFY_CANDIDATES == expected_stringify_candidates, '%r' % (
- hashi_vault_helper.STRINGIFY_CANDIDATES ^ expected_stringify_candidates
- )
-
- @pytest.mark.parametrize('input', [b'one', u'two', AnsibleUnsafeBytes(b'three'), AnsibleUnsafeText(u'four')])
- @pytest.mark.parametrize('stringify', [True, False])
- def test_get_vault_client_stringify(self, hashi_vault_helper, expected_stringify_candidates, input, stringify):
- kwargs = {
- '__no_candidate': AnsibleUnsafeText(u'value'),
- }
- expected_calls = []
- for k in expected_stringify_candidates:
- v = '%s_%s' % (k, input)
- kwargs[k] = v
- if stringify:
- expected_calls.append(mock.call(v))
-
- wrapper = mock.Mock(wraps=hashi_vault_helper._stringify)
- with mock.patch('hvac.Client'):
- with mock.patch.object(hashi_vault_helper, '_stringify', wrapper):
- hashi_vault_helper.get_vault_client(hashi_vault_stringify_args=stringify, **kwargs)
-
- assert wrapper.call_count == len(expected_calls)
- wrapper.assert_has_calls(expected_calls)
diff --git a/ansible_collections/community/hashi_vault/tests/unit/requirements.txt b/ansible_collections/community/hashi_vault/tests/unit/requirements.txt
index d8844e35b..af9366e75 100644
--- a/ansible_collections/community/hashi_vault/tests/unit/requirements.txt
+++ b/ansible_collections/community/hashi_vault/tests/unit/requirements.txt
@@ -1,15 +1,3 @@
-# the collection supports python 3.6 and higher, however the constraints for
-# earlier python versions are still needed for Ansible < 2.12 which doesn't
-# support tests/config.yml, so that unit tests (which will be skipped) won't
-# choke on installing requirements.
-hvac >= 0.10.6, != 0.10.12, != 0.10.13, < 1.0.0 ; python_version == '2.7' # bugs in 0.10.12 and 0.10.13 prevent it from working in Python 2
-hvac >= 0.10.6, < 1.0.0 ; python_version == '3.5' # py3.5 support will be dropped in 1.0.0
-hvac >= 0.10.6 ; python_version >= '3.6'
-
-# these should be satisfied naturally by the requests versions required by hvac anyway
-urllib3 >= 1.15 ; python_version >= '3.6' # we need raise_on_status for retry support to raise the correct exceptions https://github.com/urllib3/urllib3/blob/main/CHANGES.rst#115-2016-04-06
-urllib3 >= 1.15, <2.0.0 ; python_version < '3.6' # https://urllib3.readthedocs.io/en/latest/v2-roadmap.html#optimized-for-python-3-6
-
-# azure-identity 1.7.0 depends on cryptography 2.5 which drops python 2.6 support
-azure-identity < 1.7.0; python_version < '2.7'
-azure-identity; python_version >= '2.7'
+hvac
+urllib3
+azure-identity