From 9f81b3f4c4d3fed24280fd5543e8cbf36837defa Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 27 Dec 2016 17:14:27 -0500 Subject: [PATCH] Preliminary test setup and initial tests [https://app.asana.com/0/0/233732076199651] --- .gitignore | 3 ++ ci/testsettings.py | 9 ++++ pucas/ldap.py | 16 ++++-- pucas/tests.py | 125 +++++++++++++++++++++++++++++++++++++++++++++ pytest.ini | 4 ++ setup.cfg | 2 + setup.py | 2 + 7 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 ci/testsettings.py create mode 100644 pucas/tests.py create mode 100644 pytest.ini create mode 100644 setup.cfg diff --git a/.gitignore b/.gitignore index 72364f9..e2b85c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# local, project-specific +testsettings.py + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/ci/testsettings.py b/ci/testsettings.py new file mode 100644 index 0000000..3740c4d --- /dev/null +++ b/ci/testsettings.py @@ -0,0 +1,9 @@ +# minimal django settings required to run tests +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "test.db", + } +} + +SECRET_KEY = '' \ No newline at end of file diff --git a/pucas/ldap.py b/pucas/ldap.py index a4c7a83..0546227 100755 --- a/pucas/ldap.py +++ b/pucas/ldap.py @@ -21,8 +21,8 @@ def __init__(self): # retrieve settings and initialize connection ldap_servers = [] for server in settings.PUCAS_LDAP['SERVERS']: - ldap_servers = ldap3.Server(server, get_info=ldap3.ALL, - use_ssl=True) + ldap_servers.append(ldap3.Server(server, get_info=ldap3.ALL, + use_ssl=True)) server_pool = ldap3.ServerPool(ldap_servers, ldap3.ROUND_ROBIN, active=True, exhaust=5) @@ -35,6 +35,11 @@ def __init__(self): def find_user(self, netid, all_attributes=False): if netid: + # check for required settings and error if not available + required_configs = ['ATTRIBUTES', 'SEARCH_BASE', 'SEARCH_FILTER'] + if any(req not in settings.PUCAS_LDAP for req in required_configs): + raise LDAPSearchException('LDAP is not configured for user lookup') + # for testing, to see all available attributes if all_attributes: search_attributes = '*' @@ -61,7 +66,12 @@ def user_info_from_ldap(user): '''Populate django user info from ldap''' # configured mapping of user fields to ldap fields - attr_map = settings.PUCAS_LDAP['ATTRIBUTE_MAP'] + attr_map = settings.PUCAS_LDAP['ATTRIBUTE_MAP', None] + # if no map is configured, nothing to do + if not attr_map: + # is logging sufficient here? or should it be an exception + logging.warn('No attribute map configured; not populating user info from ldap') + return user_info = LDAPSearch().find_user(user.username) if user_info: diff --git a/pucas/tests.py b/pucas/tests.py new file mode 100644 index 0000000..9ecb841 --- /dev/null +++ b/pucas/tests.py @@ -0,0 +1,125 @@ +from unittest import mock +from django.conf import settings +from django.test import TestCase, override_settings +from ldap3.core.exceptions import LDAPException +import pytest + +from pucas.ldap import LDAPSearch, LDAPSearchException +from pucas.signals import cas_login + + +class TestSignals(TestCase): + # NOTE: using django TestCase for compatibility with + # django test runner + + @mock.patch('pucas.signals.user_info_from_ldap') + def test_cas_login(self, mock_userinit): + mockuser = mock.Mock() + # if create is not true, user init method should be called + # (currently not using any args besides user and created) + cas_login(mock.Mock(), mockuser, False, {}, mock.Mock(), mock.Mock()) + mock_userinit.assert_not_called() + + # if create is true, user init method should be called + cas_login(mock.Mock(), mockuser, True, {}, mock.Mock(), mock.Mock()) + mock_userinit.assert_called_with(mockuser) + + +class TestLDAPSearch(TestCase): + + ldap_servers = ['lds81', 'ldap42', 'ld4all'] + + @mock.patch('pucas.ldap.ldap3') + @override_settings(PUCAS_LDAP={'SERVERS': ldap_servers}) + def test_init(self, mockldap3): + + # initialize and then check expected behavior against + # mock ldap3 + LDAPSearch() + + test_servers = [] + for test_server in self.ldap_servers: + mockldap3.Server.assert_any_call(test_server, + get_info=mockldap3.ALL, use_ssl=True) + + # initialized servers are collected into server pool + servers = [mockldap3.Server.return_value + for test_server in self.ldap_servers] + mockldap3.ServerPool.assert_called_with(servers, + mockldap3.ROUND_ROBIN, active=True, exhaust=5) + + # server pool is used for connection + mockldap3.Connection.assert_called_with(mockldap3.ServerPool.return_value, + auto_bind=True) + + with pytest.raises(LDAPException): + mockldap3.Connection.side_effect = LDAPException + LDAPSearch() + + @mock.patch('pucas.ldap.ldap3') + @override_settings(PUCAS_LDAP={'SERVERS': ldap_servers, + 'ATTRIBUTES': ['uid', 'sn', 'ou'], + 'SEARCH_BASE': 'o=my_org', 'SEARCH_FILTER': "(uid=%(user)s)"}) + def test_find_user(self, mockldap3): + ldsearch = LDAPSearch() + + # empty netid should error + with pytest.raises(LDAPSearchException): + ldsearch.find_user(None) + with pytest.raises(LDAPSearchException): + ldsearch.find_user('') + + netid = 'jschmoe' + # simulate no results + ldsearch.conn.entries = [] + + with pytest.raises(LDAPSearchException) as search_err: + ldsearch.find_user(netid) + + assert 'No match found for %s' % netid in str(search_err) + # search should use configured values + ldsearch.conn.search.assert_called_with(settings.PUCAS_LDAP['SEARCH_BASE'], + settings.PUCAS_LDAP['SEARCH_FILTER'] % {'user': netid}, + attributes=settings.PUCAS_LDAP['ATTRIBUTES']) + + # simulate too many matches + ldsearch.conn.entries = [mock.Mock(), mock.Mock()] + with pytest.raises(LDAPSearchException) as search_err: + ldsearch.find_user(netid) + + assert 'Found more than one entry for %s' % netid in str(search_err) + + # simulate one match + userinfo = mock.Mock() + ldsearch.conn.entries = [userinfo] + assert ldsearch.find_user(netid) == userinfo + + # search for all attributes + ldsearch.find_user(netid, all_attributes=True) + # should use '*' instead of configured attributes + ldsearch.conn.search.assert_called_with(settings.PUCAS_LDAP['SEARCH_BASE'], + settings.PUCAS_LDAP['SEARCH_FILTER'] % {'user': netid}, + attributes='*') + + # with missing configs in any combination + bad_settings = [ + # nothing set + {}, + # attributes only + {'ATTRIBUTES': ['foo']}, + # search filter missing + {'ATTRIBUTES': ['foo'], 'SEARCH_BASE': 'u=foo'}, + # search base missing + {'ATTRIBUTES': ['foo'], 'SEARCH_FILTER': '(uid=u)'}, + ] + + for bad_cfg in bad_settings: + + with override_settings(PUCAS_LDAP=bad_cfg): + with pytest.raises(LDAPSearchException) as search_err: + ldsearch.find_user(netid) + assert 'LDAP is not configured for user lookup' in str(search_err) + + + + diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..cd944b5 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +DJANGO_SETTINGS_MODULE=testsettings +# look for tests in standard django test location +python_files = "**/tests.py" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9af7e6f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest \ No newline at end of file diff --git a/setup.py b/setup.py index f5ba266..99dd708 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,8 @@ long_description=README, url='https://github.com/Princeton-CDH/django-pucas', install_requires=['django-cas-ng', 'ldap3'], + setup_requires=['pytest-runner'], + tests_requires=['pytest', 'pytest-django'], author='CDH @ Princeton', author_email='digitalhumanities@princeton.edu', classifiers=[