#!/usr/bin/python

# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2005-2007
# Copyright (C) Martin Kuehl <mkhl@samba.org> 2006
#
# This is a Python port of the original in testprogs/ejs/samba3sam.js
#   
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#   
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#   
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

"""Tests for the samba3sam LDB module, which maps Samba3 LDAP to AD LDAP."""

import os
import sys
import samba
import ldb
from ldb import SCOPE_DEFAULT, SCOPE_BASE, SCOPE_SUBTREE
from samba import Ldb, substitute_var
from samba.tests import LdbTestCase, TestCaseInTempDir

datadir = os.path.join(os.path.dirname(__file__), "../../../../../testdata/samba3")

class MapBaseTestCase(TestCaseInTempDir):
    def setup_data(self, obj, ldif):
        self.assertTrue(ldif is not None)
        obj.db.add_ldif(substitute_var(ldif, obj.substvars))

    def setup_modules(self, ldb, s3, s4):
        ldb.add({"dn": "@MAP=samba3sam",
                 "@FROM": s4.basedn,
                 "@TO": "sambaDomainName=TESTS," + s3.basedn})

        ldb.add({"dn": "@MODULES",
                 "@LIST": "rootdse,paged_results,server_sort,extended_dn,asq,samldb,password_hash,operational,objectguid,rdn_name,samba3sam,partition"})

        ldb.add({"dn": "@PARTITION",
            "partition": [s4.basedn + ":" + s4.url, s3.basedn + ":" + s3.url],
            "replicateEntries": ["@SUBCLASSES", "@ATTRIBUTES", "@INDEXLIST"]})

    def setUp(self):
        super(MapBaseTestCase, self).setUp()

        def make_dn(basedn, rdn):
            return rdn + ",sambaDomainName=TESTS," + basedn

        def make_s4dn(basedn, rdn):
            return rdn + "," + basedn

        self.ldbfile = os.path.join(self.tempdir, "test.ldb")
        self.ldburl = "tdb://" + self.ldbfile

        tempdir = self.tempdir
        print tempdir

        class Target:
            """Simple helper class that contains data for a specific SAM connection."""
            def __init__(self, file, basedn, dn):
                self.file = os.path.join(tempdir, file)
                self.url = "tdb://" + self.file
                self.basedn = basedn
                self.substvars = {"BASEDN": self.basedn}
                self.db = Ldb()
                self._dn = dn

            def dn(self, rdn):
                return self._dn(rdn, self.basedn)

            def connect(self):
                return self.db.connect(self.url)

        self.samba4 = Target("samba4.ldb", "dc=vernstok,dc=nl", make_s4dn)
        self.samba3 = Target("samba3.ldb", "cn=Samba3Sam", make_dn)
        self.templates = Target("templates.ldb", "cn=templates", None)

        self.samba3.connect()
        self.templates.connect()
        self.samba4.connect()

    def tearDown(self):
        os.unlink(self.ldbfile)
        os.unlink(self.samba3.file)
        os.unlink(self.templates.file)
        os.unlink(self.samba4.file)
        super(MapBaseTestCase, self).tearDown()


class Samba3SamTestCase(MapBaseTestCase):
    def setUp(self):
        super(Samba3SamTestCase, self).setUp()
        ldb = Ldb(self.ldburl)
        self.setup_data(self.samba3, open(os.path.join(datadir, "samba3.ldif"), 'r').read())
        self.setup_data(self.templates, open(os.path.join(datadir, "provision_samba3sam_templates.ldif"), 'r').read())
        ldif = open(os.path.join(datadir, "provision_samba3sam.ldif"), 'r').read()
        ldb.add_ldif(substitute_var(ldif, self.samba4.substvars))
        self.setup_modules(ldb, self.samba3, self.samba4)
        self.ldb = Ldb(self.ldburl)

    def test_s3sam_search(self):
        print "Looking up by non-mapped attribute"
        msg = self.ldb.search(expression="(cn=Administrator)")
        self.assertEquals(len(msg), 1)
        self.assertEquals(msg[0]["cn"], "Administrator")

        print "Looking up by mapped attribute"
        msg = self.ldb.search(expression="(name=Backup Operators)")
        self.assertEquals(len(msg), 1)
        self.assertEquals(msg[0]["name"], "Backup Operators")

        print "Looking up by old name of renamed attribute"
        msg = self.ldb.search(expression="(displayName=Backup Operators)")
        self.assertEquals(len(msg), 0)

        print "Looking up mapped entry containing SID"
        msg = self.ldb.search(expression="(cn=Replicator)")
        self.assertEquals(len(msg), 1)
        print msg[0].dn
        self.assertEquals(str(msg[0].dn), "cn=Replicator,ou=Groups,dc=vernstok,dc=nl")
        self.assertEquals(msg[0]["objectSid"], "S-1-5-21-4231626423-2410014848-2360679739-552")

        print "Checking mapping of objectClass"
        oc = set(msg[0]["objectClass"])
        self.assertTrue(oc is not None)
        for i in oc:
            self.assertEquals(oc[i] == "posixGroup" or oc[i], "group")

        print "Looking up by objectClass"
        msg = self.ldb.search(expression="(|(objectClass=user)(cn=Administrator))")
        self.assertEquals(len(msg), 2)
        for i in range(len(msg)):
            self.assertEquals((str(msg[i].dn), "unixName=Administrator,ou=Users,dc=vernstok,dc=nl") or
                   (str(msg[i].dn) == "unixName=nobody,ou=Users,dc=vernstok,dc=nl"))


    def test_s3sam_modify(self):
        print "Adding a record that will be fallbacked"
        self.ldb.add({"dn": "cn=Foo", 
            "foo": "bar", 
            "blah": "Blie", 
            "cn": "Foo", 
            "showInAdvancedViewOnly": "TRUE"}
            )

        print "Checking for existence of record (local)"
        # TODO: This record must be searched in the local database, which is currently only supported for base searches
        # msg = ldb.search(expression="(cn=Foo)", ['foo','blah','cn','showInAdvancedViewOnly')]
        # TODO: Actually, this version should work as well but doesn't...
        # 
        #    
        msg = self.ldb.search(expression="(cn=Foo)", base="cn=Foo", scope=SCOPE_BASE, attrs=['foo','blah','cn','showInAdvancedViewOnly'])
        self.assertEquals(len(msg), 1)
        self.assertEquals(msg[0]["showInAdvancedViewOnly"], "TRUE")
        self.assertEquals(msg[0]["foo"], "bar")
        self.assertEquals(msg[0]["blah"], "Blie")

        print "Adding record that will be mapped"
        self.ldb.add({"dn": "cn=Niemand,cn=Users,dc=vernstok,dc=nl",
                 "objectClass": "user",
                 "unixName": "bin",
                 "sambaUnicodePwd": "geheim",
                 "cn": "Niemand"})

        print "Checking for existence of record (remote)"
        msg = self.ldb.search(expression="(unixName=bin)", 
                              attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
        self.assertEquals(len(msg), 1)
        self.assertEquals(msg[0]["cn"], "Niemand")
        self.assertEquals(msg[0]["sambaUnicodePwd"], "geheim")

        print "Checking for existence of record (local && remote)"
        msg = self.ldb.search(expression="(&(unixName=bin)(sambaUnicodePwd=geheim))", 
                         attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
        self.assertEquals(len(msg), 1)           # TODO: should check with more records
        self.assertEquals(msg[0]["cn"], "Niemand")
        self.assertEquals(msg[0]["unixName"], "bin")
        self.assertEquals(msg[0]["sambaUnicodePwd"], "geheim")

        print "Checking for existence of record (local || remote)"
        msg = self.ldb.search(expression="(|(unixName=bin)(sambaUnicodePwd=geheim))", 
                         attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
        print "got " + len(msg) + " replies"
        self.assertEquals(len(msg), 1)        # TODO: should check with more records
        self.assertEquals(msg[0]["cn"], "Niemand")
        self.assertEquals(msg[0]["unixName"] == "bin" or msg[0]["sambaUnicodePwd"], "geheim")

        print "Checking for data in destination database"
        msg = s3.db.search("(cn=Niemand)")
        self.assertTrue(len(msg) >= 1)
        self.assertEquals(msg[0]["sambaSID"], "S-1-5-21-4231626423-2410014848-2360679739-2001")
        self.assertEquals(msg[0]["displayName"], "Niemand")

        print "Adding attribute..."
        self.ldb.modify_ldif("""
dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
changetype: modify
add: description
description: Blah
""")

        print "Checking whether changes are still there..."
        msg = self.ldb.search(expression="(cn=Niemand)")
        self.assertTrue(len(msg) >= 1)
        self.assertEquals(msg[0]["cn"], "Niemand")
        self.assertEquals(msg[0]["description"], "Blah")

        print "Modifying attribute..."
        self.ldb.modify_ldif("""
dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
changetype: modify
replace: description
description: Blie
""")

        print "Checking whether changes are still there..."
        msg = self.ldb.search(expression="(cn=Niemand)")
        self.assertTrue(len(msg) >= 1)
        self.assertEquals(msg[0]["description"], "Blie")

        print "Deleting attribute..."
        self.ldb.modify_ldif("""
dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
changetype: modify
delete: description
""")

        print "Checking whether changes are no longer there..."
        msg = self.ldb.search(expression="(cn=Niemand)")
        self.assertTrue(len(msg) >= 1)
        self.assertTrue(not "description" in res[0])

        print "Renaming record..."
        self.ldb.rename("cn=Niemand,cn=Users,dc=vernstok,dc=nl", "cn=Niemand2,cn=Users,dc=vernstok,dc=nl")

        print "Checking whether DN has changed..."
        msg = self.ldb.search(expression="(cn=Niemand2)")
        self.assertEquals(len(msg), 1)
        self.assertEquals(str(msg[0].dn), "cn=Niemand2,cn=Users,dc=vernstok,dc=nl")

        print "Deleting record..."
        self.ldb.delete("cn=Niemand2,cn=Users,dc=vernstok,dc=nl")

        print "Checking whether record is gone..."
        msg = self.ldb.search(expression="(cn=Niemand2)")
        self.assertEquals(len(msg), 0)



class MapTestCase(MapBaseTestCase):
    def setUp(self):
        super(MapTestCase, self).setUp()
        ldb = Ldb(self.ldburl)
        self.setup_data(self.templates, open(os.path.join(datadir, "provision_samba3sam_templates.ldif"), 'r').read())
        ldif = open(os.path.join(datadir, "provision_samba3sam.ldif"), 'r').read()
        ldb.add_ldif(substitute_var(ldif, self.samba4.substvars))
        self.setup_modules(ldb, self.samba3, self.samba4)
        self.ldb = Ldb(self.ldburl)

    def test_map_search(self):
        print "Running search tests on mapped data"
        ldif = """
dn: """ + "sambaDomainName=TESTS,""" + self.samba3.basedn + """
objectclass: sambaDomain
objectclass: top
sambaSID: S-1-5-21-4231626423-2410014848-2360679739
sambaNextRid: 2000
sambaDomainName: TESTS"""
        self.samba3.db.add_ldif(substitute_var(ldif, self.samba3.substvars))

        print "Add a set of split records"
        ldif = """
dn: """ + self.samba4.dn("cn=X") + """
objectClass: user
cn: X
codePage: x
revision: x
dnsHostName: x
nextRid: y
lastLogon: x
description: x
objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
primaryGroupID: 1-5-21-4231626423-2410014848-2360679739-512

dn: """ + self.samba4.dn("cn=Y") + """
objectClass: top
cn: Y
codePage: x
revision: x
dnsHostName: y
nextRid: y
lastLogon: y
description: x

dn: """ + self.samba4.dn("cn=Z") + """
objectClass: top
cn: Z
codePage: x
revision: y
dnsHostName: z
nextRid: y
lastLogon: z
description: y
"""

        self.ldb.add_ldif(substitute_var(ldif, self.samba4.substvars))

        print "Add a set of remote records"

        ldif = """
dn: """ + self.samba3.dn("cn=A") + """
objectClass: posixAccount
cn: A
sambaNextRid: x
sambaBadPasswordCount: x
sambaLogonTime: x
description: x
sambaSID: S-1-5-21-4231626423-2410014848-2360679739-552
sambaPrimaryGroupSID: S-1-5-21-4231626423-2410014848-2360679739-512

dn: """ + self.samba3.dn("cn=B") + """
objectClass: top
cn:B
sambaNextRid: x
sambaBadPasswordCount: x
sambaLogonTime: y
description: x

dn: """ + self.samba3.dn("cn=C") + """
objectClass: top
cn: C
sambaNextRid: x
sambaBadPasswordCount: y
sambaLogonTime: z
description: y
"""
        self.samba3.add_ldif(substitute_var(ldif, self.samba3.substvars))

        print "Testing search by DN"

        # Search remote record by local DN
        dn = self.samba4.dn("cn=A")
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(str(res[0].dn)), dn)
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "x")

        # Search remote record by remote DN
        dn = self.samba3.dn("cn=A")
        attrs = ["dnsHostName", "lastLogon", "sambaLogonTime"]
        res = self.samba3.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(str(res[0].dn)), dn)
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertTrue(not "lastLogon" in res[0])
        self.assertEquals(res[0]["sambaLogonTime"], "x")

        # Search split record by local DN
        dn = self.samba4.dn("cn=X")
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(str(res[0].dn)), dn)
        self.assertEquals(res[0]["dnsHostName"], "x")
        self.assertEquals(res[0]["lastLogon"], "x")

        # Search split record by remote DN
        dn = self.samba3.dn("cn=X")
        attrs = ["dnsHostName", "lastLogon", "sambaLogonTime"]
        res = self.samba3.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(str(res[0].dn)), dn)
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertTrue(not "lastLogon" in res[0])
        self.assertEquals(res[0]["sambaLogonTime"], "x")

        print "Testing search by attribute"

        # Search by ignored attribute
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(revision=x)", scope=SCOPE_DEFAULT, attrs=attrs)
        self.assertEquals(len(res), 2)
        self.assertEquals(str(str(res[0].dn)), self.samba4.dn("cn=Y"))
        self.assertEquals(res[0]["dnsHostName"], "y")
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(str(res[1].dn)), self.samba4.dn("cn=X"))
        self.assertEquals(res[1]["dnsHostName"], "x")
        self.assertEquals(res[1]["lastLogon"], "x")

        # Search by kept attribute
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(description=y)", scope=SCOPE_DEFAULT, attrs=attrs)
        self.assertEquals(len(res), 2)
        self.assertEquals(str(str(res[0].dn)), self.samba4.dn("cn=Z"))
        self.assertEquals(res[0]["dnsHostName"], "z")
        self.assertEquals(res[0]["lastLogon"], "z")
        self.assertEquals(str(str(res[1].dn)), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "z")

        # Search by renamed attribute
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(badPwdCount=x)", scope=SCOPE_DEFAULT, attrs=attrs)
        self.assertEquals(len(res), 2)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "x")

        # Search by converted attribute
        attrs = ["dnsHostName", "lastLogon", "objectSid"]
        # TODO:
        #   Using the SID directly in the parse tree leads to conversion
        #   errors, letting the search fail with no results.
        #res = self.ldb.search("(objectSid=S-1-5-21-4231626423-2410014848-2360679739-552)", scope=SCOPE_DEFAULT, attrs)
        res = self.ldb.search(expression="(objectSid=*)", attrs=attrs)
        self.assertEquals(len(res), 3)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[0]["dnsHostName"], "x")
        self.assertEquals(res[0]["lastLogon"], "x")
        self.assertEquals(res[0]["objectSid"], "S-1-5-21-4231626423-2410014848-2360679739-552")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "x")
        self.assertEquals(res[1]["objectSid"], "S-1-5-21-4231626423-2410014848-2360679739-552")

        # Search by generated attribute 
        # In most cases, this even works when the mapping is missing
        # a `convert_operator' by enumerating the remote db.
        attrs = ["dnsHostName", "lastLogon", "primaryGroupID"]
        res = self.ldb.search(expression="(primaryGroupID=512)", attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "x")
        self.assertEquals(res[0]["primaryGroupID"], "512")

        # TODO: There should actually be two results, A and X.  The
        # primaryGroupID of X seems to get corrupted somewhere, and the
        # objectSid isn't available during the generation of remote (!) data,
        # which can be observed with the following search.  Also note that Xs
        # objectSid seems to be fine in the previous search for objectSid... */
        #res = ldb.search(expression="(primaryGroupID=*)", NULL, ldb. SCOPE_DEFAULT, attrs)
        #print len(res) + " results found"
        #for i in range(len(res)):
        #    for (obj in res[i]) {
        #        print obj + ": " + res[i][obj]
        #    }
        #    print "---"
        #    

        # Search by remote name of renamed attribute */
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(sambaBadPasswordCount=*)", attrs=attrs)
        self.assertEquals(len(res), 0)

        # Search by objectClass
        attrs = ["dnsHostName", "lastLogon", "objectClass"]
        res = self.ldb.search(expression="(objectClass=user)", attrs=attrs)
        self.assertEquals(len(res), 2)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[0]["dnsHostName"], "x")
        self.assertEquals(res[0]["lastLogon"], "x")
        self.assertTrue(res[0]["objectClass"] is not None)
        self.assertEquals(res[0]["objectClass"][0], "user")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "x")
        self.assertTrue(res[1]["objectClass"] is not None)
        self.assertEquals(res[1]["objectClass"][0], "user")

        # Prove that the objectClass is actually used for the search
        res = self.ldb.search(expression="(|(objectClass=user)(badPwdCount=x))", attrs=attrs)
        self.assertEquals(len(res), 3)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertTrue(res[0]["objectClass"] is not None)
        for oc in set(res[0]["objectClass"]):
            self.assertEquals(oc, "user")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[1]["dnsHostName"], "x")
        self.assertEquals(res[1]["lastLogon"], "x")
        self.assertTrue(res[1]["objectClass"] is not None)
        self.assertEquals(res[1]["objectClass"][0], "user")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[2])
        self.assertEquals(res[2]["lastLogon"], "x")
        self.assertTrue(res[2]["objectClass"] is not None)
        self.assertEquals(res[2]["objectClass"][0], "user")

        print "Testing search by parse tree"

        # Search by conjunction of local attributes
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(&(codePage=x)(revision=x))", attrs=attrs)
        self.assertEquals(len(res), 2)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
        self.assertEquals(res[0]["dnsHostName"], "y")
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[1]["dnsHostName"], "x")
        self.assertEquals(res[1]["lastLogon"], "x")

        # Search by conjunction of remote attributes
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(&(lastLogon=x)(description=x))", attrs=attrs)
        self.assertEquals(len(res), 2)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[0]["dnsHostName"], "x")
        self.assertEquals(res[0]["lastLogon"], "x")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "x")
        
        # Search by conjunction of local and remote attribute 
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(&(codePage=x)(description=x))", attrs=attrs)
        self.assertEquals(len(res), 2)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
        self.assertEquals(res[0]["dnsHostName"], "y")
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[1]["dnsHostName"], "x")
        self.assertEquals(res[1]["lastLogon"], "x")

        # Search by conjunction of local and remote attribute w/o match
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(&(codePage=x)(nextRid=x))", attrs=attrs)
        self.assertEquals(len(res), 0)
        res = self.ldb.search(expression="(&(revision=x)(lastLogon=z))", attrs=attrs)
        self.assertEquals(len(res), 0)

        # Search by disjunction of local attributes
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(|(revision=x)(dnsHostName=x))", attrs=attrs)
        self.assertEquals(len(res), 2)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
        self.assertEquals(res[0]["dnsHostName"], "y")
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[1]["dnsHostName"], "x")
        self.assertEquals(res[1]["lastLogon"], "x")

        # Search by disjunction of remote attributes
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(|(badPwdCount=x)(lastLogon=x))", attrs=attrs)
        self.assertEquals(len(res), 3)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
        self.assertTrue("dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[1]["dnsHostName"], "x")
        self.assertEquals(res[1]["lastLogon"], "x")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
        self.assertTrue("dnsHostName" in res[2])
        self.assertEquals(res[2]["lastLogon"], "x")

        # Search by disjunction of local and remote attribute
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(|(revision=x)(lastLogon=y))", attrs=attrs)
        self.assertEquals(len(res), 3)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
        self.assertEquals(res[0]["dnsHostName"], "y")
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
        self.assertTrue("dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "y")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[2]["dnsHostName"], "x")
        self.assertEquals(res[2]["lastLogon"], "x")

        # Search by disjunction of local and remote attribute w/o match
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(|(codePage=y)(nextRid=z))", attrs=attrs)
        self.assertEquals(len(res), 0)

        # Search by negated local attribute
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(!(revision=x))", attrs=attrs)
        self.assertEquals(len(res), 5)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "x")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
        self.assertEquals(res[2]["dnsHostName"], "z")
        self.assertEquals(res[2]["lastLogon"], "z")
        self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[3])
        self.assertEquals(res[3]["lastLogon"], "z")

        # Search by negated remote attribute
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(!(description=x))", attrs=attrs)
        self.assertEquals(len(res), 3)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Z"))
        self.assertEquals(res[0]["dnsHostName"], "z")
        self.assertEquals(res[0]["lastLogon"], "z")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "z")

        # Search by negated conjunction of local attributes
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(!(&(codePage=x)(revision=x)))", attrs=attrs)
        self.assertEquals(len(res), 5)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "x")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
        self.assertEquals(res[2]["dnsHostName"], "z")
        self.assertEquals(res[2]["lastLogon"], "z")
        self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[3])
        self.assertEquals(res[3]["lastLogon"], "z")

        # Search by negated conjunction of remote attributes
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(!(&(lastLogon=x)(description=x)))", attrs=attrs)
        self.assertEquals(len(res), 5)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
        self.assertEquals(res[0]["dnsHostName"], "y")
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "y")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
        self.assertEquals(res[2]["dnsHostName"], "z")
        self.assertEquals(res[2]["lastLogon"], "z")
        self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[3])
        self.assertEquals(res[3]["lastLogon"], "z")

        # Search by negated conjunction of local and remote attribute
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(!(&(codePage=x)(description=x)))", attrs=attrs)
        self.assertEquals(len(res), 5)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "x")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
        self.assertEquals(res[2]["dnsHostName"], "z")
        self.assertEquals(res[2]["lastLogon"], "z")
        self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[3])
        self.assertEquals(res[3]["lastLogon"], "z")

        # Search by negated disjunction of local attributes
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(!(|(revision=x)(dnsHostName=x)))", attrs=attrs)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[1])
        self.assertEquals(res[1]["lastLogon"], "x")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
        self.assertEquals(res[2]["dnsHostName"], "z")
        self.assertEquals(res[2]["lastLogon"], "z")
        self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[3])
        self.assertEquals(res[3]["lastLogon"], "z")

        # Search by negated disjunction of remote attributes
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(!(|(badPwdCount=x)(lastLogon=x)))", attrs=attrs)
        self.assertEquals(len(res), 4)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
        self.assertEquals(res[0]["dnsHostName"], "y")
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
        self.assertEquals(res[1]["dnsHostName"], "z")
        self.assertEquals(res[1]["lastLogon"], "z")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[2])
        self.assertEquals(res[2]["lastLogon"], "z")

        # Search by negated disjunction of local and remote attribute
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(!(|(revision=x)(lastLogon=y)))", attrs=attrs)
        self.assertEquals(len(res), 4)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "x")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
        self.assertEquals(res[1]["dnsHostName"], "z")
        self.assertEquals(res[1]["lastLogon"], "z")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[2])
        self.assertEquals(res[2]["lastLogon"], "z")

        print "Search by complex parse tree"
        attrs = ["dnsHostName", "lastLogon"]
        res = self.ldb.search(expression="(|(&(revision=x)(dnsHostName=x))(!(&(description=x)(nextRid=y)))(badPwdCount=y))", attrs=attrs)
        self.assertEquals(len(res), 6)
        self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
        self.assertTrue(not "dnsHostName" in res[0])
        self.assertEquals(res[0]["lastLogon"], "y")
        self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
        self.assertEquals(res[1]["dnsHostName"], "x")
        self.assertEquals(res[1]["lastLogon"], "x")
        self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
        self.assertTrue(not "dnsHostName" in res[2])
        self.assertEquals(res[2]["lastLogon"], "x")
        self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z"))
        self.assertEquals(res[3]["dnsHostName"], "z")
        self.assertEquals(res[3]["lastLogon"], "z")
        self.assertEquals(str(res[4].dn), self.samba4.dn("cn=C"))
        self.assertTrue(not "dnsHostName" in res[4])
        self.assertEquals(res[4]["lastLogon"], "z")

        # Clean up
        dns = [self.samba4.dn("cn=%s" % n) for n in ["A","B","C","X","Y","Z"]]
        for dn in dns:
            self.ldb.delete(dn)

    def test_map_modify_local(self):
        """Modification of local records."""
        # Add local record
        dn = "cn=test,dc=idealx,dc=org"
        self.ldb.add({"dn": dn, 
                 "cn": "test",
                 "foo": "bar",
                 "revision": "1",
                 "description": "test"})
        # Check it's there
        attrs = ["foo", "revision", "description"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertEquals(res[0]["foo"], "bar")
        self.assertEquals(res[0]["revision"], "1")
        self.assertEquals(res[0]["description"], "test")
        # Check it's not in the local db
        res = self.samba4.db.search(expression="(cn=test)", scope=SCOPE_DEFAULT, attrs=attrs)
        self.assertEquals(len(res), 0)
        # Check it's not in the remote db
        res = self.samba3.db.search(expression="(cn=test)", scope=SCOPE_DEFAULT, attrs=attrs)
        self.assertEquals(len(res), 0)

        # Modify local record
        ldif = """
dn: """ + dn + """
replace: foo
foo: baz
replace: description
description: foo
"""
        self.ldb.modify_ldif(ldif)
        # Check in local db
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertEquals(res[0]["foo"], "baz")
        self.assertEquals(res[0]["revision"], "1")
        self.assertEquals(res[0]["description"], "foo")

        # Rename local record
        dn2 = "cn=toast,dc=idealx,dc=org"
        self.ldb.rename(dn, dn2)
        # Check in local db
        res = self.ldb.search(dn2, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn2)
        self.assertEquals(res[0]["foo"], "baz")
        self.assertEquals(res[0]["revision"], "1")
        self.assertEquals(res[0]["description"], "foo")

        # Delete local record
        self.ldb.delete(dn2)
        # Check it's gone
        res = self.ldb.search(dn2, scope=SCOPE_BASE)
        self.assertEquals(len(res), 0)

    def test_map_modify_remote_remote(self):
        """Modification of remote data of remote records"""
        # Add remote record
        dn = self.samba4.dn("cn=test")
        dn2 = self.samba3.dn("cn=test")
        self.samba3.db.add({"dn": dn2, 
                   "cn": "test",
                   "description": "foo",
                   "sambaBadPasswordCount": "3",
                   "sambaNextRid": "1001"})
        # Check it's there
        attrs = ["description", "sambaBadPasswordCount", "sambaNextRid"]
        res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn2)
        self.assertEquals(res[0]["description"], "foo")
        self.assertEquals(res[0]["sambaBadPasswordCount"], "3")
        self.assertEquals(res[0]["sambaNextRid"], "1001")
        # Check in mapped db
        attrs = ["description", "badPwdCount", "nextRid"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertEquals(res[0]["description"], "foo")
        self.assertEquals(res[0]["badPwdCount"], "3")
        self.assertEquals(res[0]["nextRid"], "1001")
        # Check in local db
        res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 0)

        # Modify remote data of remote record
        ldif = """
dn: """ + dn + """
replace: description
description: test
replace: badPwdCount
badPwdCount: 4
"""
        self.ldb.modify_ldif(ldif)
        # Check in mapped db
        attrs = ["description", "badPwdCount", "nextRid"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertEquals(res[0]["description"], "test")
        self.assertEquals(res[0]["badPwdCount"], "4")
        self.assertEquals(res[0]["nextRid"], "1001")
        # Check in remote db
        attrs = ["description", "sambaBadPasswordCount", "sambaNextRid"]
        res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn2)
        self.assertEquals(res[0]["description"], "test")
        self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
        self.assertEquals(res[0]["sambaNextRid"], "1001")

        # Rename remote record
        dn2 = self.samba4.dn("cn=toast")
        self.ldb.rename(dn, dn2)
        # Check in mapped db
        dn = dn2
        attrs = ["description", "badPwdCount", "nextRid"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertEquals(res[0]["description"], "test")
        self.assertEquals(res[0]["badPwdCount"], "4")
        self.assertEquals(res[0]["nextRid"], "1001")
        # Check in remote db 
        dn2 = self.samba3.dn("cn=toast")
        attrs = ["description", "sambaBadPasswordCount", "sambaNextRid"]
        res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn2)
        self.assertEquals(res[0]["description"], "test")
        self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
        self.assertEquals(res[0]["sambaNextRid"], "1001")

        # Delete remote record
        self.ldb.delete(dn)
        # Check in mapped db
        res = self.ldb.search(dn, scope=SCOPE_BASE)
        self.assertEquals(len(res), 0)
        # Check in remote db
        res = self.samba3.db.search(dn2, scope=SCOPE_BASE)
        self.assertEquals(len(res), 0)

    def test_map_modify_remote_local(self):
        """Modification of local data of remote records"""
        # Add remote record (same as before)
        dn = self.samba4.dn("cn=test")
        dn2 = self.samba3.dn("cn=test")
        self.samba3.db.add({"dn": dn2, 
                   "cn": "test",
                   "description": "foo",
                   "sambaBadPasswordCount": "3",
                   "sambaNextRid": "1001"})

        # Modify local data of remote record
        ldif = """
dn: """ + dn + """
add: revision
revision: 1
replace: description
description: test
"""
        self.ldb.modify_ldif(ldif)
        # Check in mapped db
        attrs = ["revision", "description"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertEquals(res[0]["description"], "test")
        self.assertEquals(res[0]["revision"], "1")
        # Check in remote db
        res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn2)
        self.assertEquals(res[0]["description"], "test")
        self.assertTrue(not "revision" in res[0])
        # Check in local db
        res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertTrue(not "description" in res[0])
        self.assertEquals(res[0]["revision"], "1")

        # Delete (newly) split record
        self.ldb.delete(dn)

    def test_map_modify_split(self):
        """Testing modification of split records"""
        # Add split record
        dn = self.samba4.dn("cn=test")
        dn2 = self.samba3.dn("cn=test")
        self.ldb.add({
            "dn": dn,
            "cn": "test",
            "description": "foo",
            "badPwdCount": "3",
            "nextRid": "1001",
            "revision": "1"})
        # Check it's there
        attrs = ["description", "badPwdCount", "nextRid", "revision"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertEquals(res[0]["description"], "foo")
        self.assertEquals(res[0]["badPwdCount"], "3")
        self.assertEquals(res[0]["nextRid"], "1001")
        self.assertEquals(res[0]["revision"], "1")
        # Check in local db
        res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertTrue(not "description" in res[0])
        self.assertTrue(not "badPwdCount" in res[0])
        self.assertTrue(not "nextRid" in res[0])
        self.assertEquals(res[0]["revision"], "1")
        # Check in remote db
        attrs = ["description", "sambaBadPasswordCount", "sambaNextRid", "revision"]
        res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn2)
        self.assertEquals(res[0]["description"], "foo")
        self.assertEquals(res[0]["sambaBadPasswordCount"], "3")
        self.assertEquals(res[0]["sambaNextRid"], "1001")
        self.assertTrue(not "revision" in res[0])

        # Modify of split record
        ldif = """
dn: """ + dn + """
replace: description
description: test
replace: badPwdCount
badPwdCount: 4
replace: revision
revision: 2
"""
        self.ldb.modify_ldif(ldif)
        # Check in mapped db
        attrs = ["description", "badPwdCount", "nextRid", "revision"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertEquals(res[0]["description"], "test")
        self.assertEquals(res[0]["badPwdCount"], "4")
        self.assertEquals(res[0]["nextRid"], "1001")
        self.assertEquals(res[0]["revision"], "2")
        # Check in local db
        res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertTrue(not "description" in res[0])
        self.assertTrue(not "badPwdCount" in res[0])
        self.assertTrue(not "nextRid" in res[0])
        self.assertEquals(res[0]["revision"], "2")
        # Check in remote db
        attrs = ["description", "sambaBadPasswordCount", "sambaNextRid", "revision"]
        res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn2)
        self.assertEquals(res[0]["description"], "test")
        self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
        self.assertEquals(res[0]["sambaNextRid"], "1001")
        self.assertTrue(not "revision" in res[0])

        # Rename split record
        dn2 = self.samba4.dn("cn=toast")
        self.ldb.rename(dn, dn2)
        # Check in mapped db
        dn = dn2
        attrs = ["description", "badPwdCount", "nextRid", "revision"]
        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertEquals(res[0]["description"], "test")
        self.assertEquals(res[0]["badPwdCount"], "4")
        self.assertEquals(res[0]["nextRid"], "1001")
        self.assertEquals(res[0]["revision"], "2")
        # Check in local db
        res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn)
        self.assertTrue(not "description" in res[0])
        self.assertTrue(not "badPwdCount" in res[0])
        self.assertTrue(not "nextRid" in res[0])
        self.assertEquals(res[0]["revision"], "2")
        # Check in remote db
        dn2 = self.samba3.dn("cn=toast")
        attrs = ["description", "sambaBadPasswordCount", "sambaNextRid", "revision"]
        res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
        self.assertEquals(len(res), 1)
        self.assertEquals(str(res[0].dn), dn2)
        self.assertEquals(res[0]["description"], "test")
        self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
        self.assertEquals(res[0]["sambaNextRid"], "1001")
        self.assertTrue(not "revision" in res[0])

        # Delete split record
        self.ldb.delete(dn)
        # Check in mapped db
        res = self.ldb.search(dn, scope=SCOPE_BASE)
        self.assertEquals(len(res), 0)
        # Check in local db
        res = self.samba4.db.search(dn, scope=SCOPE_BASE)
        self.assertEquals(len(res), 0)
        # Check in remote db
        res = self.samba3.db.search(dn2, scope=SCOPE_BASE)
        self.assertEquals(len(res), 0)