mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
module_utils.postgres: added unittests (#56381)
This commit is contained in:
parent
f1b5836836
commit
386cef18ed
2 changed files with 311 additions and 4 deletions
|
@ -27,9 +27,9 @@
|
||||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
psycopg2 = None # This line needs for unit tests
|
||||||
try:
|
try:
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from psycopg2.extras import DictCursor
|
|
||||||
HAS_PSYCOPG2 = True
|
HAS_PSYCOPG2 = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_PSYCOPG2 = False
|
HAS_PSYCOPG2 = False
|
||||||
|
@ -55,6 +55,11 @@ def ensure_libs(sslrootcert=None):
|
||||||
|
|
||||||
|
|
||||||
def postgres_common_argument_spec():
|
def postgres_common_argument_spec():
|
||||||
|
"""
|
||||||
|
Return a dictionary with connection options.
|
||||||
|
|
||||||
|
The options are commonly used by most of PostgreSQL modules.
|
||||||
|
"""
|
||||||
return dict(
|
return dict(
|
||||||
login_user=dict(default='postgres'),
|
login_user=dict(default='postgres'),
|
||||||
login_password=dict(default='', no_log=True),
|
login_password=dict(default='', no_log=True),
|
||||||
|
@ -67,6 +72,7 @@ def postgres_common_argument_spec():
|
||||||
|
|
||||||
|
|
||||||
def ensure_required_libs(module):
|
def ensure_required_libs(module):
|
||||||
|
"""Check required libraries."""
|
||||||
if not HAS_PSYCOPG2:
|
if not HAS_PSYCOPG2:
|
||||||
module.fail_json(msg=missing_required_lib('psycopg2'))
|
module.fail_json(msg=missing_required_lib('psycopg2'))
|
||||||
|
|
||||||
|
@ -75,7 +81,14 @@ def ensure_required_libs(module):
|
||||||
|
|
||||||
|
|
||||||
def connect_to_db(module, autocommit=False, fail_on_conn=True, warn_db_default=True):
|
def connect_to_db(module, autocommit=False, fail_on_conn=True, warn_db_default=True):
|
||||||
|
"""Return psycopg2 connection object.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
module -- object of ansible.module_utils.basic.AnsibleModule class
|
||||||
|
autocommit -- commit automatically (default False)
|
||||||
|
fail_on_conn -- fail if connection failed or just warn and return None (default True)
|
||||||
|
warn_db_default -- warn that the default DB is used (default True)
|
||||||
|
"""
|
||||||
ensure_required_libs(module)
|
ensure_required_libs(module)
|
||||||
|
|
||||||
# To use defaults values, keyword arguments must be absent, so
|
# To use defaults values, keyword arguments must be absent, so
|
||||||
|
@ -110,21 +123,23 @@ def connect_to_db(module, autocommit=False, fail_on_conn=True, warn_db_default=T
|
||||||
if is_localhost and module.params["login_unix_socket"] != "":
|
if is_localhost and module.params["login_unix_socket"] != "":
|
||||||
kw["host"] = module.params["login_unix_socket"]
|
kw["host"] = module.params["login_unix_socket"]
|
||||||
|
|
||||||
|
db_connection = None
|
||||||
try:
|
try:
|
||||||
db_connection = psycopg2.connect(**kw)
|
db_connection = psycopg2.connect(**kw)
|
||||||
if autocommit:
|
if autocommit:
|
||||||
if psycopg2.__version__ >= '2.4.2':
|
if LooseVersion(psycopg2.__version__) >= LooseVersion('2.4.2'):
|
||||||
db_connection.set_session(autocommit=True)
|
db_connection.set_session(autocommit=True)
|
||||||
else:
|
else:
|
||||||
db_connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
db_connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||||
|
|
||||||
# Switch role, if specified:
|
# Switch role, if specified:
|
||||||
cursor = db_connection.cursor(cursor_factory=DictCursor)
|
|
||||||
if module.params.get('session_role'):
|
if module.params.get('session_role'):
|
||||||
|
cursor = db_connection.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||||
try:
|
try:
|
||||||
cursor.execute('SET ROLE %s' % module.params['session_role'])
|
cursor.execute('SET ROLE %s' % module.params['session_role'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg="Could not switch role: %s" % to_native(e))
|
module.fail_json(msg="Could not switch role: %s" % to_native(e))
|
||||||
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
|
|
292
test/units/module_utils/postgresql/test_postgres.py
Normal file
292
test/units/module_utils/postgresql/test_postgres.py
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
# Copyright: (c) 2019, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import ansible.module_utils.postgres as pg
|
||||||
|
|
||||||
|
|
||||||
|
class TestPostgresCommonArgSpec():
|
||||||
|
|
||||||
|
"""
|
||||||
|
Namespace for testing postgresql_common_arg_spec() function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_postgres_common_argument_spec(self):
|
||||||
|
"""
|
||||||
|
Test for postgresql_common_arg_spec() function.
|
||||||
|
|
||||||
|
The tested function just returns a dictionary with the default
|
||||||
|
parameters and their values for PostgreSQL modules.
|
||||||
|
The return and expected dictionaries must be compared.
|
||||||
|
"""
|
||||||
|
expected_dict = dict(
|
||||||
|
login_user=dict(default='postgres'),
|
||||||
|
login_password=dict(default='', no_log=True),
|
||||||
|
login_host=dict(default=''),
|
||||||
|
login_unix_socket=dict(default=''),
|
||||||
|
port=dict(type='int', default=5432, aliases=['login_port']),
|
||||||
|
ssl_mode=dict(
|
||||||
|
default='prefer',
|
||||||
|
choices=['allow', 'disable', 'prefer', 'require', 'verify-ca', 'verify-full']
|
||||||
|
),
|
||||||
|
ca_cert=dict(aliases=['ssl_rootcert']),
|
||||||
|
)
|
||||||
|
assert pg.postgres_common_argument_spec() == expected_dict
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def m_psycopg2():
|
||||||
|
"""Return mock object for psycopg2 emulation."""
|
||||||
|
global Cursor
|
||||||
|
Cursor = None
|
||||||
|
|
||||||
|
class Cursor():
|
||||||
|
def __init__(self):
|
||||||
|
self.passed_query = None
|
||||||
|
|
||||||
|
def execute(self, query):
|
||||||
|
self.passed_query = query
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
global DbConnection
|
||||||
|
DbConnection = None
|
||||||
|
|
||||||
|
class DbConnection():
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cursor(self, cursor_factory=None):
|
||||||
|
return Cursor()
|
||||||
|
|
||||||
|
def set_session(self, autocommit=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_isolation_level(self, isolevel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Extras():
|
||||||
|
def __init__(self):
|
||||||
|
self.DictCursor = True
|
||||||
|
|
||||||
|
class Extensions():
|
||||||
|
def __init__(self):
|
||||||
|
self.ISOLATION_LEVEL_AUTOCOMMIT = True
|
||||||
|
|
||||||
|
class DummyPsycopg2():
|
||||||
|
def __init__(self):
|
||||||
|
self.__version__ = '2.4.3'
|
||||||
|
self.extras = Extras()
|
||||||
|
self.extensions = Extensions()
|
||||||
|
|
||||||
|
def connect(self, host=None, port=None, user=None,
|
||||||
|
password=None, sslmode=None, sslrootcert=None):
|
||||||
|
if user == 'Exception':
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
return DbConnection()
|
||||||
|
|
||||||
|
return DummyPsycopg2()
|
||||||
|
|
||||||
|
|
||||||
|
class TestEnsureReqLibs():
|
||||||
|
|
||||||
|
"""
|
||||||
|
Namespace for testing ensure_required_libs() function.
|
||||||
|
|
||||||
|
If there is something wrong with libs, the function invokes fail_json()
|
||||||
|
method of AnsibleModule object passed as an argument called 'module'.
|
||||||
|
Therefore we must check:
|
||||||
|
1. value of err_msg attribute of m_ansible_module mock object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def m_ansible_module(self, scope='class'):
|
||||||
|
"""Return an object of dummy AnsibleModule class."""
|
||||||
|
class Dummym_ansible_module():
|
||||||
|
def __init__(self):
|
||||||
|
self.params = {'ca_cert': False}
|
||||||
|
self.err_msg = ''
|
||||||
|
|
||||||
|
def fail_json(self, msg):
|
||||||
|
self.err_msg = msg
|
||||||
|
|
||||||
|
return Dummym_ansible_module()
|
||||||
|
|
||||||
|
def test_ensure_req_libs_has_not_psycopg2(self, m_ansible_module):
|
||||||
|
"""Test ensure_required_libs() with psycopg2 is None."""
|
||||||
|
# HAS_PSYCOPG2 is False by default
|
||||||
|
pg.ensure_required_libs(m_ansible_module)
|
||||||
|
assert 'Failed to import the required Python library (psycopg2)' in m_ansible_module.err_msg
|
||||||
|
|
||||||
|
def test_ensure_req_libs_has_psycopg2(self, m_ansible_module, monkeypatch):
|
||||||
|
"""Test ensure_required_libs() with psycopg2 is not None."""
|
||||||
|
monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True)
|
||||||
|
|
||||||
|
pg.ensure_required_libs(m_ansible_module)
|
||||||
|
assert m_ansible_module.err_msg == ''
|
||||||
|
|
||||||
|
def test_ensure_req_libs_ca_cert(self, m_ansible_module, m_psycopg2, monkeypatch):
|
||||||
|
"""
|
||||||
|
Test with module.params['ca_cert'], psycopg2 version is suitable.
|
||||||
|
"""
|
||||||
|
m_ansible_module.params['ca_cert'] = True
|
||||||
|
monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True)
|
||||||
|
monkeypatch.setattr(pg, 'psycopg2', m_psycopg2)
|
||||||
|
|
||||||
|
pg.ensure_required_libs(m_ansible_module)
|
||||||
|
assert m_ansible_module.err_msg == ''
|
||||||
|
|
||||||
|
def test_ensure_req_libs_ca_cert_low_psycopg2_ver(self, m_ansible_module, m_psycopg2, monkeypatch):
|
||||||
|
"""
|
||||||
|
Test with module.params['ca_cert'], psycopg2 version is wrong.
|
||||||
|
"""
|
||||||
|
m_ansible_module.params['ca_cert'] = True
|
||||||
|
monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True)
|
||||||
|
# Set wrong psycopg2 version number:
|
||||||
|
psycopg2 = m_psycopg2
|
||||||
|
psycopg2.__version__ = '2.4.2'
|
||||||
|
monkeypatch.setattr(pg, 'psycopg2', psycopg2)
|
||||||
|
|
||||||
|
pg.ensure_required_libs(m_ansible_module)
|
||||||
|
assert 'psycopg2 must be at least 2.4.3' in m_ansible_module.err_msg
|
||||||
|
|
||||||
|
|
||||||
|
class TestConnectToDb():
|
||||||
|
|
||||||
|
"""
|
||||||
|
Namespace for testing connect_to_db() function.
|
||||||
|
|
||||||
|
When some connection errors occure connect_to_db() caught any of them
|
||||||
|
and invoke fail_json() or warn() methods of AnsibleModule object
|
||||||
|
depending on the passed parameters.
|
||||||
|
connect_to_db may return db_connection object or None if errors occured.
|
||||||
|
Therefore we must check:
|
||||||
|
1. Values of err_msg and warn_msg attributes of m_ansible_module mock object.
|
||||||
|
2. Types of return objects (db_connection and cursor).
|
||||||
|
"""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def m_ansible_module(self, scope='class'):
|
||||||
|
"""Return an object of dummy AnsibleModule class."""
|
||||||
|
class DummyAnsibleModule():
|
||||||
|
def __init__(self):
|
||||||
|
self.params = pg.postgres_common_argument_spec()
|
||||||
|
self.err_msg = ''
|
||||||
|
self.warn_msg = ''
|
||||||
|
|
||||||
|
def fail_json(self, msg):
|
||||||
|
self.err_msg = msg
|
||||||
|
|
||||||
|
def warn(self, msg):
|
||||||
|
self.warn_msg = msg
|
||||||
|
|
||||||
|
return DummyAnsibleModule()
|
||||||
|
|
||||||
|
def test_connect_to_db(self, m_ansible_module, monkeypatch, m_psycopg2):
|
||||||
|
"""Test connect_to_db(), common test."""
|
||||||
|
monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True)
|
||||||
|
monkeypatch.setattr(pg, 'psycopg2', m_psycopg2)
|
||||||
|
|
||||||
|
db_connection = pg.connect_to_db(m_ansible_module)
|
||||||
|
cursor = db_connection.cursor()
|
||||||
|
# if errors, db_connection returned as None:
|
||||||
|
assert isinstance(db_connection, DbConnection)
|
||||||
|
assert isinstance(cursor, Cursor)
|
||||||
|
assert m_ansible_module.err_msg == ''
|
||||||
|
# The default behaviour, normal in this case:
|
||||||
|
assert 'Database name has not been passed' in m_ansible_module.warn_msg
|
||||||
|
|
||||||
|
def test_session_role(self, m_ansible_module, monkeypatch, m_psycopg2):
|
||||||
|
"""Test connect_to_db(), switch on session_role."""
|
||||||
|
monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True)
|
||||||
|
monkeypatch.setattr(pg, 'psycopg2', m_psycopg2)
|
||||||
|
|
||||||
|
m_ansible_module.params['session_role'] = 'test_role'
|
||||||
|
db_connection = pg.connect_to_db(m_ansible_module)
|
||||||
|
cursor = db_connection.cursor()
|
||||||
|
# if errors, db_connection returned as None:
|
||||||
|
assert isinstance(db_connection, DbConnection)
|
||||||
|
assert isinstance(cursor, Cursor)
|
||||||
|
assert m_ansible_module.err_msg == ''
|
||||||
|
# The default behaviour, normal in this case:
|
||||||
|
assert 'Database name has not been passed' in m_ansible_module.warn_msg
|
||||||
|
|
||||||
|
def test_warn_db_default_non_default(self, m_ansible_module, monkeypatch, m_psycopg2):
|
||||||
|
"""
|
||||||
|
Test connect_to_db(), warn_db_default arg passed as False (by default is True).
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True)
|
||||||
|
monkeypatch.setattr(pg, 'psycopg2', m_psycopg2)
|
||||||
|
|
||||||
|
db_connection = pg.connect_to_db(m_ansible_module, warn_db_default=False)
|
||||||
|
cursor = db_connection.cursor()
|
||||||
|
# if errors, db_connection returned as None:
|
||||||
|
assert isinstance(db_connection, DbConnection)
|
||||||
|
assert isinstance(cursor, Cursor)
|
||||||
|
assert m_ansible_module.err_msg == ''
|
||||||
|
assert m_ansible_module.warn_msg == ''
|
||||||
|
# pay attention that warn_db_defaul=True has been checked
|
||||||
|
# in the previous tests by
|
||||||
|
# assert('Database name has not been passed' in m_ansible_module.warn_msg)
|
||||||
|
# because of this is the default behavior
|
||||||
|
|
||||||
|
def test_fail_on_conn_true(self, m_ansible_module, monkeypatch, m_psycopg2):
|
||||||
|
"""
|
||||||
|
Test connect_to_db(), fail_on_conn arg passed as True (the default behavior).
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True)
|
||||||
|
monkeypatch.setattr(pg, 'psycopg2', m_psycopg2)
|
||||||
|
|
||||||
|
m_ansible_module.params['login_user'] = 'Exception' # causes Exception
|
||||||
|
|
||||||
|
db_connection = pg.connect_to_db(m_ansible_module, fail_on_conn=True)
|
||||||
|
|
||||||
|
assert 'unable to connect to database' in m_ansible_module.err_msg
|
||||||
|
assert db_connection is None
|
||||||
|
|
||||||
|
def test_fail_on_conn_false(self, m_ansible_module, monkeypatch, m_psycopg2):
|
||||||
|
"""
|
||||||
|
Test connect_to_db(), fail_on_conn arg passed as False.
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True)
|
||||||
|
monkeypatch.setattr(pg, 'psycopg2', m_psycopg2)
|
||||||
|
|
||||||
|
m_ansible_module.params['login_user'] = 'Exception' # causes Exception
|
||||||
|
|
||||||
|
db_connection = pg.connect_to_db(m_ansible_module, fail_on_conn=False)
|
||||||
|
|
||||||
|
assert m_ansible_module.err_msg == ''
|
||||||
|
assert 'PostgreSQL server is unavailable' in m_ansible_module.warn_msg
|
||||||
|
assert db_connection is None
|
||||||
|
|
||||||
|
def test_autocommit_true(self, m_ansible_module, monkeypatch, m_psycopg2):
|
||||||
|
"""
|
||||||
|
Test connect_to_db(), autocommit arg passed as True (the default is False).
|
||||||
|
"""
|
||||||
|
monkeypatch.setattr(pg, 'HAS_PSYCOPG2', True)
|
||||||
|
|
||||||
|
# case 1: psycopg2.__version >= 2.4.2 (the default in m_psycopg2)
|
||||||
|
monkeypatch.setattr(pg, 'psycopg2', m_psycopg2)
|
||||||
|
|
||||||
|
db_connection = pg.connect_to_db(m_ansible_module, autocommit=True)
|
||||||
|
cursor = db_connection.cursor()
|
||||||
|
|
||||||
|
# if errors, db_connection returned as None:
|
||||||
|
assert isinstance(db_connection, DbConnection)
|
||||||
|
assert isinstance(cursor, Cursor)
|
||||||
|
assert m_ansible_module.err_msg == ''
|
||||||
|
|
||||||
|
# case 2: psycopg2.__version < 2.4.2
|
||||||
|
m_psycopg2.__version__ = '2.4.1'
|
||||||
|
monkeypatch.setattr(pg, 'psycopg2', m_psycopg2)
|
||||||
|
|
||||||
|
db_connection = pg.connect_to_db(m_ansible_module, autocommit=True)
|
||||||
|
cursor = db_connection.cursor()
|
||||||
|
|
||||||
|
# if errors, db_connection returned as None:
|
||||||
|
assert isinstance(db_connection, DbConnection)
|
||||||
|
assert isinstance(cursor, Cursor)
|
||||||
|
assert 'psycopg2 must be at least 2.4.3' in m_ansible_module.err_msg
|
Loading…
Reference in a new issue