ldap_faker
Most of the purpose of python-ldap-faker
is to make automated testing of code that uses python-ldap
easier.
To this end, python-ldap-faker
provides :pyLDAPFakerMixin
, a mixin class for :pyunittest.TestCase
which handles all the hard work of patching and instrumenting the appropriate python-ldap
functions, objects and methods.
:pyLDAPFakerMixin
will do the following things for you:
- Read data from JSON fixture files to populate one or more :py
ObjectStore
objects (our fake LDAP server class) - Associate those :py
ObjectStore
objects with particular LDAP URIs - Patch :py
ldap.initialize
to return :pyFakeLDAPObject
objects configured with the appropriate :pyObjectStore
for the LDAP URI passed into :pyFakeLDAP.initialize
We need to set two class attributes on :pyLDAPFakerMixin
in order for it to properly set up your tests:
- :py
LDAPFakerMixin.ldap_modules
: The list of your code's modules in which to patch :pyldap.initialize
, :pyldap.set_option
and :pyldap.get_option
` - :py
LDAPFakerMixin.ldap_fixtures
: A list of JSON fixture files with which to create the :pyObjectStore
objects
:pyLDAPFakerMixin
uses :pyunittest.mock.patch
to patch your code so that it uses our fake versions of :pyldap.initialize
, :pyldap.set_option
and :pyldap.get_option
instead of the real one. The way patch
works is that it must apply the patch within the context of your module that does import ldap
, not within the ldap
module itself. Thus, to make :pyLDAPFakerMixin
work for you, you must list all the modules for code under test in which you do import ldap
.
To list all the modules in which the code under test does import ldap
, use the :pyLDAPFakerMixin.ldap_modules
class attribute.
For example, if you have a class MyLDAPUsingClass
in the module myapp.myldapstuff
, and you do import ldap
in myapp.myldapstuff
, for instance:
import ldap
class MyLDAPUsingClass:
def connect(self, uid: str, password: str):
self.conn = ldap.initialize('ldap://server')
self.conn.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
self.conn.start_tls_s()
self.conn.simple_bind_s(
f'uid={uid},ou=bar,o=baz,c=country',
'the password'
)
To test this code, you would use this for ldap_modules
:
import unittest
from ldap_faker import LDAPFakerMixin
from myapp.myldapstuff import MyLDAPUsingClass
class TestMyLDAPUsingCLass(LDAPFakerMixin, unittest.TestCase):
ldap_modules = ['myapp.myldapstuff']
In order to effectively test your python-ldap
using code, you'll need to populate an :pyLDAPServerFactory
one or more :pyObjectStore
objects bound to LDAP URIs. We use :pyLDAPFakerMixin.ldap_fixtures
to declare file paths to fixture files to use to populate those :pyObjectClass
objects.
- Fixture files are JSON files in the format described in
File format for ObjectStore.load_objects
. - File paths are either absolute paths or are treated as relative to the folder in which your
TestCase
resides. - Fixtures are loaded into the :py
LDAPServerFactory
once per :pyunittest.TestCase
via the :pyunittest.TestCase.setUpClass
classmethod.
You can configure your :pyLDAPFakerMixin
to use fixtures one of two ways:
- Use a single default fixture that will be used no matter which LDAP URI is passed to :py
FakeLDAP.initialize
- Bind each fixture to specific a LDAP URI. This allows you simulate talking to several different LDAP servers.
Note
When binding fixtures to particular LDAP URIs, if your tries to use :pyFakeLDAP.initialize
with an LDAP URI that was not explicitly configured, python-ldap-faker
will raise :pyldap.SERVER_DOWN
This form sets up one default fixture:
import unittest
from ldap_faker import LDAPFakerMixin
from myapp.myldapstuff import MyLDAPUsingClass
class TestMyLDAPUsingCLass(LDAPFakerMixin, unittest.TestCase):
ldap_fixtures = 'objects.json'
This form binds fixtures to LDAP URIs:
import unittest
from ldap_faker import LDAPFakerMixin
from myapp.myldapstuff import MyLDAPUsingClass
class TestMyLDAPUsingCLass(LDAPFakerMixin, unittest.TestCase):
ldap_fixtures = [
('server1.json', 'ldap://server1.example.com'),
('server2.json', 'ldap://server2.example.com')
]
Each test method on your :pyunittest.TestCase
will get a fresh, unaltered copy of the fixture data, and connections, call histories, options set from previous test methods will be cleared.
For each test you run, your test will have access to the :pyFakeLDAP
instance used for that test through the :pyLDAPFakerMixin.fake_ldap
instance attribute. Each test gets a fresh :pyFakeLDAP
instance.
Note
For detailed information on any of the below, see the api
.
Some things to know about your :pyFakeLDAP
instance:
- :py
FakeLDAP.connections
lists all the :pyFakeLDAPObject
connections created during your test method, in the order they were made. One such object is created each time :pyFakeLDAP.initialize
is called by your code. - :py
FakeLDAP.options
is a :pyOptionStore
object that records all the global LDAP options set during your test - :py
FakeLDAP.calls
is a :pyCallHistory
object that records calls (with arguments) to :pyFakeLDAP.initialize
, :pyFakeLDAP.set_option
, :pyFakeLDAP.get_option
Some things to know about the :pyFakeLDAPObject
objects in :pyFakeLDAP.connections
:
- :py
FakeLDAPObject.uri
is the LDAP URI requested - :py
FakeLDAPObject.store
is our :pyObjectStore
copy - :py
FakeLDAP.options
is a :pyOptionStore
object that records all the LDAP options set on this connection during your test method - :py
FakeLDAPObject.calls
is a :pyCallHistory
that records allpython-ldap
api calls (with arguments) that your code made to thisFakeLDAPObject
- :py
FakeLDAPObject.bound_dn
is thedn
of the user bound viasimple_bind_s
, if any. If this isNone
, we did anonymous binding. - :py
FakeLDAPObject.tls_enabled
will be set toTrue
ifstart_tls_s
was used on this connection