"""
Test cases for ldaptor.protocols.ldap.ldapsyntax module.
"""

from twisted.trial import unittest
from OPSI.ldaptor import config, testutil, delta
from OPSI.ldaptor.protocols.ldap import ldapsyntax, ldaperrors
from OPSI.ldaptor.protocols import pureldap, pureber
from OPSI.twisted.internet import defer
from OPSI.twisted.python import failure
from OPSI.ldaptor.testutil import LDAPClientTestDriver

class LDAPSyntaxBasics(unittest.TestCase):
    def testCreation(self):
        """Creating an LDAP object should succeed."""
        client = LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            })
        self.failUnlessEqual(str(o.dn), 'cn=foo,dc=example,dc=com')
        self.failUnlessEqual(o['objectClass'], ['a', 'b'])
        self.failUnlessEqual(o['aValue'], ['a'])
        self.failUnlessEqual(o['bValue'], ['b'])
        client.assertNothingSent()

    def testKeys(self):
        """Iterating over the keys of an LDAP object gives expected results."""
        client = LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            })
        seen={}
        for k in o.keys():
            assert not seen.has_key(k)
            seen[k]=1
        assert seen == {'objectClass': 1,
                        'aValue': 1,
                        'bValue': 1,
                        }

    def testItems(self):
        """Iterating over the items of an LDAP object gives expected results."""
        client = LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            })
        seen={}
        for k,vs in o.items():
            assert not seen.has_key(k)
            seen[k]=vs
        assert seen == {'objectClass': ['a', 'b'],
                        'aValue': ['a'],
                        'bValue': ['b'],
                        }

    def testIn(self):
        """Key in object gives expected results."""
        client=LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            })
        assert 'objectClass' in o
        assert 'aValue' in o
        assert 'bValue' in o
        assert 'foo' not in o
        assert '' not in o
        assert None not in o

        assert 'a' in o['objectClass']
        assert 'b' in o['objectClass']
        assert 'foo' not in o['objectClass']
        assert '' not in o['objectClass']
        assert None not in o['objectClass']

        assert 'a' in o['aValue']
        assert 'foo' not in o['aValue']
        assert '' not in o['aValue']
        assert None not in o['aValue']

class LDAPSyntaxAttributes(unittest.TestCase):
    def testAttributeSetting(self):
        client=LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            })
        o['aValue']=['foo', 'bar']
        self.failUnlessEqual(o['aValue'], ['foo', 'bar'])
        o['aValue']=['quux']
        self.failUnlessEqual(o['aValue'], ['quux'])
        self.failUnlessEqual(o['bValue'], ['b'])
        o['cValue']=['thud']
        self.failUnlessEqual(o['aValue'], ['quux'])
        self.failUnlessEqual(o['bValue'], ['b'])
        self.failUnlessEqual(o['cValue'], ['thud'])

    def testAttributeDelete(self):
        client=LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            })
        o['aValue']=['quux']
        del o['aValue']
        del o['bValue']
        self.failIf(o.has_key('aValue'))
        self.failIf(o.has_key('bValue'))

    def testAttributeAdd(self):
        client=LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            })
        o['aValue'].add('foo')
        self.failUnlessEqual(o['aValue'], ['a', 'foo'])

    def testAttributeItemDelete(self):
        client=LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a', 'b', 'c'],
            'bValue': ['b'],
            })
        o['aValue'].remove('b')
        self.failUnlessEqual(o['aValue'], ['a', 'c'])

    def testUndo(self):
        """Undo should forget the modifications."""
        client=LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            'cValue': ['c'],
            })
        o['aValue']=['foo', 'bar']
        o['aValue']=['quux']
        del o['cValue']
        o.undo()
        self.failUnlessEqual(o['aValue'], ['a'])
        self.failUnlessEqual(o['bValue'], ['b'])
        self.failUnlessEqual(o['cValue'], ['c'])

    def testUndoJournaling(self):
        """Journaling should still work after undo."""
        client=LDAPClientTestDriver(
            [ pureldap.LDAPModifyResponse(resultCode=0,
                                          matchedDN='',
                                          errorMessage=''),
            ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            'cValue': ['c'],
            })
        o['aValue']=['foo', 'bar']
        o['aValue']=['quux']
        del o['cValue']
        o.undo()
        o['aValue'].update(['newValue', 'anotherNewValue'])
        d=o.commit()
        def cb(dummy):
            self.failUnlessEqual(o['aValue'], ['a', 'newValue', 'anotherNewValue'])
            self.failUnlessEqual(o['bValue'], ['b'])
            self.failUnlessEqual(o['cValue'], ['c'])
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Add('aValue', ['newValue', 'anotherNewValue']),
                ]).asLDAP())
        d.addCallback(cb)
        return d

    def testUndoAfterCommit(self):
        """Undo should not undo things that have been commited."""

        client=LDAPClientTestDriver(
            [ pureldap.LDAPModifyResponse(resultCode=0,
                                          matchedDN='',
                                          errorMessage=''),
            ])
        o=ldapsyntax.LDAPEntry(
            client=client,
            dn='cn=foo,dc=example,dc=com',
            attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            'bValue': ['b'],
            'cValue': ['c'],
            })
        o['aValue']=['foo', 'bar']
        o['bValue']=['quux']
        del o['cValue']

        d=o.commit()
        def cb(dummy):
            o.undo()
            self.failUnlessEqual(o['aValue'], ['foo', 'bar'])
            self.failUnlessEqual(o['bValue'], ['quux'])
            self.failIf(o.has_key('cValue'))
        d.addCallback(cb)
        return d

class LDAPSyntaxAttributesModificationOnWire(unittest.TestCase):
    def testAdd(self):
        """Modify & commit should write the right data to the server."""

        client = LDAPClientTestDriver(
            [   pureldap.LDAPModifyResponse(resultCode=0,
                                            matchedDN='',
                                            errorMessage=''),
                ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            })
        o['aValue'].update(['newValue', 'anotherNewValue'])

        d=o.commit()
        def cb(dummy):
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Add('aValue', ['newValue', 'anotherNewValue']),
                ]).asLDAP())
        d.addCallback(cb)
        return d

    def testAddSeparate(self):
        """Modify & commit should write the right data to the server."""

        client = LDAPClientTestDriver(
            [   pureldap.LDAPModifyResponse(resultCode=0,
                                            matchedDN='',
                                            errorMessage=''),
                ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            })
        o['aValue'].add('newValue')
        o['aValue'].add('anotherNewValue')

        d=o.commit()
        def cb(dummy):
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Add('aValue', ['newValue']),
                delta.Add('aValue', ['anotherNewValue']),
                ]).asLDAP())
        d.addCallback(cb)
        return d

    def testDeleteAttribute(self):
        """Modify & commit should write the right data to the server."""

        client = LDAPClientTestDriver(
            [   pureldap.LDAPModifyResponse(resultCode=0,
                                            matchedDN='',
                                            errorMessage='')
                ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            })
        o['aValue'].remove('a')

        d=o.commit()
        def cb(dummy):
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Delete('aValue', ['a']),
                ]).asLDAP())
        d.addCallback(cb)
        return d

    def testDeleteAllAttribute(self):
        """Modify & commit should write the right data to the server."""

        client = LDAPClientTestDriver(
            [   pureldap.LDAPModifyResponse(resultCode=0,
                                            matchedDN='',
                                            errorMessage=''),
                ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a1', 'a2'],
            'bValue': ['b1', 'b2'],
            })
        del o['aValue']
        o['bValue'].clear()

        d=o.commit()
        def cb(dummy):
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Delete('aValue'),
                delta.Delete('bValue'),
                ]).asLDAP())
        d.addCallback(cb)
        return d


    def testReplaceAttributes(self):
        """Modify & commit should write the right data to the server."""

        client = LDAPClientTestDriver(
            [   pureldap.LDAPModifyResponse(resultCode=0,
                                            matchedDN='',
                                            errorMessage='')
                ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            })
        o['aValue']=['foo', 'bar']

        d=o.commit()
        def cb(dummy):
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('aValue', ['foo', 'bar']),
                ]).asLDAP())
        d.addCallback(cb)
        return d


class LDAPSyntaxSearch(unittest.TestCase):
    def testSearch(self):
        """Test searches."""

        client=LDAPClientTestDriver([
            pureldap.LDAPSearchResultEntry(
            objectName='cn=foo,dc=example,dc=com',
            attributes=(('foo', ['a']),
                        ('bar', ['b', 'c']),
                        ),
            ),
            pureldap.LDAPSearchResultEntry(
            objectName='cn=bar,dc=example,dc=com',
            attributes=(('foo', ['a']),
                        ('bar', ['d', 'e']),
                        ),
            ),
            pureldap.LDAPSearchResultDone(
            resultCode=0,
            matchedDN='',
            errorMessage='')
            ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='dc=example,dc=com',
                               attributes={
            'objectClass': ['organizationalUnit'],
            })

        d=o.search(filterText='(foo=a)',
                   attributes=['foo', 'bar'])
        def cb(val):
            client.assertSent(pureldap.LDAPSearchRequest(
                baseObject='dc=example,dc=com',
                scope=pureldap.LDAP_SCOPE_wholeSubtree,
                derefAliases=pureldap.LDAP_DEREF_neverDerefAliases,
                sizeLimit=0,
                timeLimit=0,
                typesOnly=0,
                filter=pureldap.LDAPFilter_equalityMatch(
                attributeDesc=pureldap.LDAPAttributeDescription(value='foo'),
                assertionValue=pureldap.LDAPAssertionValue(value='a')),
                attributes=['foo', 'bar']))
            self.failUnlessEqual(len(val), 2)

            self.failUnlessEqual(val[0],
                                 ldapsyntax.LDAPEntry(
                client=client,
                dn='cn=foo,dc=example,dc=com',
                attributes={
                'foo': ['a'],
                'bar': ['b', 'c'],
                }))

            self.failUnlessEqual(val[1],
                                 ldapsyntax.LDAPEntry(
                client=client,
                dn='cn=bar,dc=example,dc=com',
                attributes={
                'foo': ['a'],
                'bar': ['d', 'e'],
                }))
        d.addCallback(cb)
        return d


    def testSearch_defaultAttributes(self):
        """Search without explicit list of attributes returns all attributes."""

        client=LDAPClientTestDriver([
            pureldap.LDAPSearchResultEntry(
            objectName='cn=foo,dc=example,dc=com',
            attributes=(('foo', ['a']),
                        ('bar', ['b', 'c']),
                        ),
            ),
            pureldap.LDAPSearchResultEntry(
            objectName='cn=bar,dc=example,dc=com',
            attributes=(('foo', ['a']),
                        ('bar', ['d', 'e']),
                        ),
            ),
            pureldap.LDAPSearchResultDone(
            resultCode=0,
            matchedDN='',
            errorMessage='')
            ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='dc=example,dc=com',
                               attributes={
            'objectClass': ['organizationalUnit'],
            })

        d=o.search(filterText='(foo=a)')
        def cb(val):
            client.assertSent(pureldap.LDAPSearchRequest(
                baseObject='dc=example,dc=com',
                scope=pureldap.LDAP_SCOPE_wholeSubtree,
                derefAliases=pureldap.LDAP_DEREF_neverDerefAliases,
                sizeLimit=0,
                timeLimit=0,
                typesOnly=0,
                filter=pureldap.LDAPFilter_equalityMatch(
                attributeDesc=pureldap.LDAPAttributeDescription(value='foo'),
                assertionValue=pureldap.LDAPAssertionValue(value='a')),
                attributes=[]))
            self.failUnlessEqual(len(val), 2)

            self.failUnlessEqual(val[0],
                                 ldapsyntax.LDAPEntry(
                client=client,
                dn='cn=foo,dc=example,dc=com',
                attributes={
                'foo': ['a'],
                'bar': ['b', 'c'],
                }))
            self.failUnless(val[0].complete)

            self.failUnlessEqual(val[1],
                                 ldapsyntax.LDAPEntry(
                client=client,
                dn='cn=bar,dc=example,dc=com',
                attributes={
                'foo': ['a'],
                'bar': ['d', 'e'],
                }))
            self.failUnless(val[1].complete)
        d.addCallback(cb)
        return d

    def testSearch_noAttributes(self):
        """Search with attributes=None returns no attributes."""

        client=LDAPClientTestDriver([
            pureldap.LDAPSearchResultEntry('cn=foo,dc=example,dc=com',
                                           attributes=()),
            pureldap.LDAPSearchResultEntry('cn=bar,dc=example,dc=com',
                                           attributes=()),
            pureldap.LDAPSearchResultDone(
            resultCode=0,
            matchedDN='',
            errorMessage='')
            ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='dc=example,dc=com',
                               attributes={
            'objectClass': ['organizationalUnit'],
            })

        d=o.search(filterText='(foo=a)',
                   attributes=None)
        def cb(val):
            client.assertSent(pureldap.LDAPSearchRequest(
                baseObject='dc=example,dc=com',
                scope=pureldap.LDAP_SCOPE_wholeSubtree,
                derefAliases=pureldap.LDAP_DEREF_neverDerefAliases,
                sizeLimit=0,
                timeLimit=0,
                typesOnly=0,
                filter=pureldap.LDAPFilter_equalityMatch(
                attributeDesc=pureldap.LDAPAttributeDescription(value='foo'),
                assertionValue=pureldap.LDAPAssertionValue(value='a')),
                attributes=['1.1']))
            self.failUnlessEqual(len(val), 2)

            self.failUnlessEqual(val[0],
                                 ldapsyntax.LDAPEntry(
                client=client,
                dn='cn=foo,dc=example,dc=com'))
            self.failIf(val[0].complete)

            self.failUnlessEqual(val[1],
                                 ldapsyntax.LDAPEntry(
                client=client,
                dn='cn=bar,dc=example,dc=com'))
            self.failIf(val[1].complete)
        d.addCallback(cb)
        return d

    def testSearch_ImmediateProcessing(self):
        """Test searches with the immediate processing feature."""

        client=LDAPClientTestDriver([
            pureldap.LDAPSearchResultEntry(
            objectName='cn=foo,dc=example,dc=com',
            attributes=(('bar', ['b', 'c']),
                        ),
            ),

            pureldap.LDAPSearchResultEntry(
            objectName='cn=bar,dc=example,dc=com',
            attributes=(('bar', ['b', 'c']),
                        ),
            ),

            pureldap.LDAPSearchResultDone(
            resultCode=0,
            matchedDN='',
            errorMessage='')
            ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='dc=example,dc=com',
                               attributes={
            'objectClass': ['organizationalUnit'],
            })

        seen=[]
        def process(o):
            seen.append(o)

        d=o.search(filterText='(foo=a)',
                   attributes=['bar'],
                   callback=process)
        def cb(val):
            self.assertEquals(val, None)

            client.assertSent(pureldap.LDAPSearchRequest(
                baseObject='dc=example,dc=com',
                scope=pureldap.LDAP_SCOPE_wholeSubtree,
                derefAliases=pureldap.LDAP_DEREF_neverDerefAliases,
                sizeLimit=0,
                timeLimit=0,
                typesOnly=0,
                filter=pureldap.LDAPFilter_equalityMatch(
                attributeDesc=pureldap.LDAPAttributeDescription(value='foo'),
                assertionValue=pureldap.LDAPAssertionValue(value='a')),
                attributes=['bar']))

            self.failUnlessEqual(seen,
                                 [
                ldapsyntax.LDAPEntry(
                client=client,
                dn='cn=foo,dc=example,dc=com',
                attributes={
                'bar': ['b', 'c'],
                }),
                ldapsyntax.LDAPEntry(
                client=client,
                dn='cn=bar,dc=example,dc=com',
                attributes={
                'bar': ['b', 'c'],
                })])
        d.addCallback(cb)
        return d

    def testSearch_fail(self):
        client=LDAPClientTestDriver([
            pureldap.LDAPSearchResultDone(
            resultCode=ldaperrors.LDAPBusy.resultCode,
            matchedDN='',
            errorMessage='Go away')
            ])

        o=ldapsyntax.LDAPEntry(client=client, dn='dc=example,dc=com')
        d=o.search(filterText='(foo=a)')
        def eb(fail):
            fail.trap(ldaperrors.LDAPBusy)
            self.assertEquals(fail.value.message, 'Go away')

            client.assertSent(pureldap.LDAPSearchRequest(
                baseObject='dc=example,dc=com',
                scope=pureldap.LDAP_SCOPE_wholeSubtree,
                derefAliases=pureldap.LDAP_DEREF_neverDerefAliases,
                sizeLimit=0,
                timeLimit=0,
                typesOnly=0,
                filter=pureldap.LDAPFilter_equalityMatch(
                attributeDesc=pureldap.LDAPAttributeDescription(value='foo'),
                assertionValue=pureldap.LDAPAssertionValue(value='a')),
                ))
        d.addCallbacks(testutil.mustRaise, eb)
        return d

class LDAPSyntaxDNs(unittest.TestCase):
    def testDNKeyExistenceSuccess(self):
        client = LDAPClientTestDriver()
        ldapsyntax.LDAPEntry(client=client,
                             dn='cn=foo,dc=example,dc=com',
                             attributes={
            'cn': ['foo'],
            })

    def TODOtestDNKeyExistenceFailure(self):
        client = LDAPClientTestDriver()
        self.failUnlessRaises(ldapsyntax.DNNotPresentError,
                              ldapsyntax.LDAPEntry,
                              client=client,
                              dn='cn=foo,dc=example,dc=com',
                              attributes={
            'foo': ['bar'],
            })

class LDAPSyntaxLDIF(unittest.TestCase):
    def testLDIFConversion(self):
        client = LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a', 'b'],
            'bValue': ['c'],
            })
        self.failUnlessEqual(str(o),
                             '\n'.join((
            "dn: cn=foo,dc=example,dc=com",
            "objectClass: a",
            "objectClass: b",
            "aValue: a",
            "aValue: b",
            "bValue: c",
            "\n")))

class LDAPSyntaxDelete(unittest.TestCase):
    def testDeleteInvalidates(self):
        """Deleting an LDAPEntry invalidates it."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPDelResponse(resultCode=0,
                                      matchedDN='',
                                      errorMessage=''),
             ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a'],
            })
        d=o.delete()
        def cb(dummy):
            self.failUnlessRaises(
                ldapsyntax.ObjectDeletedError,
                o.search,
                filterText='(foo=a)')
            self.failUnlessRaises(
                ldapsyntax.ObjectDeletedError,
                o.get,
                'objectClass')
        d.addCallback(cb)
        return d

    def testDeleteOnWire(self):
        """LDAPEntry.delete should write the right data to the server."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPDelResponse(resultCode=0,
                                      matchedDN='',
                                      errorMessage=''),
             ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a'],
            })
        d=o.delete()
        def cb(dummy):
            client.assertSent(pureldap.LDAPDelRequest(
                entry='cn=foo,dc=example,dc=com',
                ))
        d.addCallback(cb)
        return d

    def testErrorHandling(self):
        """LDAPEntry.delete should pass LDAP errors to it's deferred."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPDelResponse(resultCode=ldaperrors.LDAPBusy.resultCode,
                                      matchedDN='',
                                      errorMessage='Go away'),
             ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a'],
            })
        d=o.delete()
        def eb(fail):
            fail.trap(ldaperrors.LDAPBusy)
            self.assertEquals(fail.value.message, 'Go away')

            client.assertSent(pureldap.LDAPDelRequest(
                entry='cn=foo,dc=example,dc=com',
                ))
        d.addCallbacks(testutil.mustRaise, eb)
        return d

    def testErrorHandling_extended(self):
        """LDAPEntry.delete should pass even non-LDAPDelResponse errors to it's deferred."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=ldaperrors.LDAPProtocolError.resultCode,
                                                 responseName='1.3.6.1.4.1.1466.20036',
                                                 errorMessage='Unknown request')
             ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a'],
            })
        d=o.delete()
        def eb(fail):
            fail.trap(ldaperrors.LDAPProtocolError)
            self.assertEquals(fail.value.message, 'Unknown request')

            client.assertSent(pureldap.LDAPDelRequest(
                entry='cn=foo,dc=example,dc=com',
                ))
        d.addCallbacks(testutil.mustRaise, eb)
        return d

class LDAPSyntaxAddChild(unittest.TestCase):
    def testAddChildOnWire(self):
        """LDAPEntry.addChild should write the right data to the server."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPAddResponse(resultCode=0,
                                      matchedDN='',
                                      errorMessage=''),
             ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='ou=things,dc=example,dc=com',
                               attributes={
            'objectClass': ['organizationalUnit'],
            'ou': ['things'],
            })
        d=o.addChild(
            rdn='givenName=Firstname+surname=Lastname',
            attributes={'objectClass': ['person', 'otherStuff'],
                        'givenName': ['Firstname'],
                        'surname': ['Lastname'],
                        })
        def cb(dummy):
            client.assertSent(pureldap.LDAPAddRequest(
                entry='givenName=Firstname+surname=Lastname,ou=things,dc=example,dc=com',
                attributes=[ (pureldap.LDAPAttributeDescription('objectClass'),
                              pureber.BERSet([pureldap.LDAPAttributeValue('person'),
                                              pureldap.LDAPAttributeValue('otherStuff'),
                                              ])),
                             (pureldap.LDAPAttributeDescription('givenName'),
                              pureber.BERSet([pureldap.LDAPAttributeValue('Firstname')])),
                             (pureldap.LDAPAttributeDescription('surname'),
                              pureber.BERSet([pureldap.LDAPAttributeValue('Lastname')])),
                             ],
                ))
        d.addCallback(cb)
        return d

class LDAPSyntaxContainingNamingContext(unittest.TestCase):
    def testNamingContext(self):
        """LDAPEntry.namingContext returns the naming context that contains this object (via a Deferred)."""
        client=LDAPClientTestDriver(
            [   pureldap.LDAPSearchResultEntry(
            objectName='',
            attributes=[('namingContexts',
                         ('dc=foo,dc=example',
                          'dc=example,dc=com',
                          'dc=bar,dc=example',
                          ))]),

                pureldap.LDAPSearchResultDone(resultCode=0,
                                              matchedDN='',
                                              errorMessage='')
            ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,ou=bar,dc=example,dc=com',
                               attributes={
            'objectClass': ['a'],
            })
        d=o.namingContext()
        def cb(p):
            assert isinstance(p, ldapsyntax.LDAPEntry)
            assert p.client == o.client
            assert str(p.dn) == 'dc=example,dc=com'

            client.assertSent(pureldap.LDAPSearchRequest(
                baseObject='',
                scope=pureldap.LDAP_SCOPE_baseObject,
                filter=pureldap.LDAPFilter_present('objectClass'),
                attributes=['namingContexts'],
                ))
        d.addCallback(cb)
        return d


class LDAPSyntaxPasswords(unittest.TestCase):
    def setUp(self):
        cfg = config.loadConfig()
        cfg.set('samba', 'use-lmhash', 'no')

    def testPasswordSetting_ExtendedOperation(self):
        """LDAPEntry.setPassword_ExtendedOperation(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=0,
                                           matchedDN='',
                                           errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com')
        d=o.setPassword_ExtendedOperation(newPasswd='new')
        def cb(dummy):
            client.assertSent(pureldap.LDAPPasswordModifyRequest(
                userIdentity='cn=foo,dc=example,dc=com',
                newPasswd='new'),
                              )
        d.addCallback(cb)
        return d

    def testPasswordSetting_Samba_sambaAccount(self):
        """LDAPEntry.setPassword_Samba(newPasswd=...,
        style='sambaAccount') changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com')
        d=o.setPassword_Samba(newPasswd='new', style='sambaAccount')
        def cb(dummy):
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('ntPassword',
                              ['89963F5042E5041A59C249282387A622']),
                delta.Replace('lmPassword',
                              ['XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']),
                ]).asLDAP())
        d.addCallback(cb)
        return d

    def testPasswordSetting_Samba_sambaSamAccount(self):
        """LDAPEntry.setPassword_Samba(newPasswd=..., style='sambaSamAccount') changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com')
        d=o.setPassword_Samba(newPasswd='new', style='sambaSamAccount')
        def cb(dummy):
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('sambaNTPassword',
                              ['89963F5042E5041A59C249282387A622']),
                delta.Replace('sambaLMPassword',
                              ['XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']),
                ]).asLDAP())
        d.addCallback(cb)
        return d

    def testPasswordSetting_Samba_defaultStyle(self):
        """LDAPEntry.setPassword_Samba(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com')
        d=o.setPassword_Samba(newPasswd='new')
        def cb(dummy):
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('sambaNTPassword',
                              ['89963F5042E5041A59C249282387A622']),
                            delta.Replace('sambaLMPassword',
                                          ['XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']),
                ]).asLDAP())
        d.addCallback(cb)
        return d

    def testPasswordSetting_Samba_badStyle(self):
        """LDAPEntry.setPassword_Samba(..., style='foo') fails."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com')
        d=defer.maybeDeferred(o.setPassword_Samba, newPasswd='new', style='foo')
        def eb(fail):
            fail.trap(RuntimeError)
            self.assertEquals(fail.getErrorMessage(),
                              "Unknown samba password style 'foo'")
            client.assertNothingSent()
        d.addCallbacks(testutil.mustRaise, eb)
        return d

    def testPasswordSettingAll_noSamba(self):
        """LDAPEntry.setPassword(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=0,
                                           matchedDN='',
                                           errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['foo'],
            },
                               complete=1)
        d=o.setPassword(newPasswd='new')
        def cb(dummy):
            client.assertSent(pureldap.LDAPPasswordModifyRequest(
                userIdentity='cn=foo,dc=example,dc=com',
                newPasswd='new'),
                              )
        d.addCallback(cb)
        return d


    def testPasswordSettingAll_hasSamba(self):
        """LDAPEntry.setPassword(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=0,
                                           matchedDN='',
                                           errorMessage='')],
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['foo', 'sambaAccount'],
            },
                               complete=1)
        d=o.setPassword(newPasswd='new')
        def cb(dummy):
            client.assertSent(pureldap.LDAPPasswordModifyRequest(
                userIdentity='cn=foo,dc=example,dc=com',
                newPasswd='new'),
                              delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('ntPassword',
                              ['89963F5042E5041A59C249282387A622']),
                delta.Replace('lmPassword',
                              ['XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']),
                ]).asLDAP())
        d.addCallback(cb)
        return d


    def testPasswordSettingAll_hasSambaSam(self):
        """LDAPEntry.setPassword(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=0,
                                           matchedDN='',
                                           errorMessage='')],
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['foo', 'sambaSamAccount'],
            },
                               complete=1)
        d=o.setPassword(newPasswd='new')
        def cb(dummy):
            client.assertSent(pureldap.LDAPPasswordModifyRequest(
                userIdentity='cn=foo,dc=example,dc=com',
                newPasswd='new'),
                              delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('sambaNTPassword',
                              ['89963F5042E5041A59C249282387A622']),
                delta.Replace('sambaLMPassword',
                              ['XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']),
                ]).asLDAP())
        d.addCallback(cb)
        return d


    def testPasswordSettingAll_hasSamba_differentCase(self):
        """LDAPEntry.setPassword(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=0,
                                           matchedDN='',
                                           errorMessage='')],
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['foo', 'saMBaAccOuNT'],
            },
                               complete=1)
        d=o.setPassword(newPasswd='new')
        def cb(dummy):
            client.assertSent(pureldap.LDAPPasswordModifyRequest(
                userIdentity='cn=foo,dc=example,dc=com',
                newPasswd='new'),
                              delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('ntPassword',
                              ['89963F5042E5041A59C249282387A622']),
                delta.Replace('lmPassword',
                              ['XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']),
                ]).asLDAP())
        d.addCallback(cb)
        return d


    def testPasswordSettingAll_hasSambaSam_differentCase(self):
        """LDAPEntry.setPassword(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=0,
                                           matchedDN='',
                                           errorMessage='')],
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['foo', 'sAmbASAmaccoUnt'],
            },
                               complete=1)
        d=o.setPassword(newPasswd='new')
        def cb(dummy):
            client.assertSent(pureldap.LDAPPasswordModifyRequest(
                userIdentity='cn=foo,dc=example,dc=com',
                newPasswd='new'),
                              delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('sambaNTPassword',
                              ['89963F5042E5041A59C249282387A622']),
                delta.Replace('sambaLMPassword',
                              ['XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']),
                ]).asLDAP())
        d.addCallback(cb)
        return d


    def testPasswordSettingAll_maybeSamba_WillFind(self):
        """LDAPEntry.setPassword(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=0,
                                           matchedDN='',
                                           errorMessage='')],
            [
            pureldap.LDAPSearchResultEntry(objectName='',
                                           attributes=[('objectClass',
                                                        ('foo',
                                                         'sambaAccount',
                                                         'bar'))]),
            pureldap.LDAPSearchResultDone(resultCode=0,
                                          matchedDN='',
                                          errorMessage=''),
            ],
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client, dn='cn=foo,dc=example,dc=com')
        d=o.setPassword(newPasswd='new')
        def cb(dummy):
            client.assertSent(
                pureldap.LDAPPasswordModifyRequest(userIdentity='cn=foo,dc=example,dc=com',
                                                   newPasswd='new'),
                pureldap.LDAPSearchRequest(baseObject='cn=foo,dc=example,dc=com',
                                           scope=pureldap.LDAP_SCOPE_baseObject,
                                           derefAliases=pureldap.LDAP_DEREF_neverDerefAliases,
                                           sizeLimit=0,
                                           timeLimit=0,
                                           typesOnly=0,
                                           filter=pureldap.LDAPFilterMatchAll,
                                           attributes=('objectClass',)),
                delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('ntPassword', ['89963F5042E5041A59C249282387A622']),
                delta.Replace('lmPassword', ['XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']),
                ]).asLDAP(),
                )
        d.addCallback(cb)
        return d

    def testPasswordSettingAll_maybeSamba_WillNotFind(self):
        """LDAPEntry.setPassword(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=0,
                                           matchedDN='',
                                           errorMessage='')],
            [pureldap.LDAPSearchResultEntry(objectName='',
                                            attributes=[('objectClass',
                                                         ('foo',
                                                          'bar'))]),
             pureldap.LDAPSearchResultDone(resultCode=0,
                                          matchedDN='',
                                          errorMessage=''),
            ],
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client, dn='cn=foo,dc=example,dc=com')
        d=o.setPassword(newPasswd='new')
        def cb(dummy):
            client.assertSent(
                pureldap.LDAPPasswordModifyRequest(userIdentity='cn=foo,dc=example,dc=com',
                                                   newPasswd='new'),
                pureldap.LDAPSearchRequest(baseObject='cn=foo,dc=example,dc=com',
                                           scope=pureldap.LDAP_SCOPE_baseObject,
                                           derefAliases=pureldap.LDAP_DEREF_neverDerefAliases,
                                           sizeLimit=0,
                                           timeLimit=0,
                                           typesOnly=0,
                                           filter=pureldap.LDAPFilterMatchAll,
                                           attributes=('objectClass',)),
                )
        d.addCallback(cb)
        return d

    def testPasswordSettingAll_maybeSamba_WillNotFindAnything(self):
        """LDAPEntry.setPassword(newPasswd=...) changes the password."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=0,
                                           matchedDN='',
                                           errorMessage='')],
            [
             pureldap.LDAPSearchResultDone(resultCode=0,
                                          matchedDN='',
                                          errorMessage=''),
            ],
            [pureldap.LDAPModifyResponse(resultCode=0,
                                         matchedDN='',
                                         errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client, dn='cn=foo,dc=example,dc=com')
        d=o.setPassword(newPasswd='new')

        def checkError(fail):
            fail.trap(ldapsyntax.PasswordSetAggregateError)
            l=fail.value.errors
            assert len(l)==1
            assert len(l[0])==2
            assert l[0][0]=='Samba'
            assert isinstance(l[0][1], failure.Failure)
            l[0][1].trap(ldapsyntax.DNNotPresentError)
            return 'This test run should succeed'

        def chainMustErrback(dummy):
            raise RuntimeError('Should never get here')
        d.addCallbacks(callback=chainMustErrback, errback=checkError)
        d.addCallback(self.assertEquals, 'This test run should succeed')
        def cb(dummy):
            client.assertSent(
                pureldap.LDAPPasswordModifyRequest(userIdentity='cn=foo,dc=example,dc=com',
                                                   newPasswd='new'),
                pureldap.LDAPSearchRequest(baseObject='cn=foo,dc=example,dc=com',
                                           scope=pureldap.LDAP_SCOPE_baseObject,
                                           derefAliases=pureldap.LDAP_DEREF_neverDerefAliases,
                                           sizeLimit=0,
                                           timeLimit=0,
                                           typesOnly=0,
                                           filter=pureldap.LDAPFilterMatchAll,
                                           attributes=('objectClass',)),
                )
        d.addCallback(cb)
        return d

    def testPasswordSetting_abortsOnFirstError(self):
        """LDAPEntry.setPassword() aborts on first error (does not parallelize, as it used to)."""
        client = LDAPClientTestDriver(
            [pureldap.LDAPExtendedResponse(resultCode=ldaperrors.LDAPInsufficientAccessRights.resultCode,
                                           matchedDN='',
                                           errorMessage='')],
            )

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['foo', 'sambaAccount'],
            },
                               complete=1)
        d=o.setPassword(newPasswd='new')
        def eb(fail):
            fail.trap(ldapsyntax.PasswordSetAggregateError)
            l=fail.value.errors
            assert len(l)==2

            assert len(l[0])==2
            self.assertEquals(l[0][0], 'ExtendedOperation')
            assert isinstance(l[0][1], failure.Failure)
            l[0][1].trap(ldaperrors.LDAPInsufficientAccessRights)

            assert len(l[1])==2
            self.assertEquals(l[1][0], 'Samba')
            assert isinstance(l[1][1], failure.Failure)
            l[1][1].trap(ldapsyntax.PasswordSetAborted)

            client.assertSent(pureldap.LDAPPasswordModifyRequest(
                userIdentity='cn=foo,dc=example,dc=com',
                newPasswd='new'),
                              )
        d.addCallbacks(testutil.mustRaise, eb)
        return d


class LDAPSyntaxFetch(unittest.TestCase):
    def testFetch_WithDirtyJournal(self):
        """Trying to fetch attributes with a dirty journal fails."""
        client = LDAPClientTestDriver()
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com')
        o['x']=['foo']

        self.failUnlessRaises(
            ldapsyntax.ObjectDirtyError,
            o.fetch)

    def testFetch_Empty(self):
        """Fetching attributes for a newly-created object works."""
        client = LDAPClientTestDriver(
            [   pureldap.LDAPSearchResultEntry(objectName='cn=foo,dc=example,dc=com',
                                               attributes=(
            ('foo', ['a']),
            ('bar', ['b', 'c']),
            )),
                pureldap.LDAPSearchResultDone(resultCode=0,
                                              matchedDN='',
                                              errorMessage=''),
                ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com')
        d=o.fetch()
        def cb(dummy):
            client.assertSent(pureldap.LDAPSearchRequest(
                baseObject='cn=foo,dc=example,dc=com',
                scope=pureldap.LDAP_SCOPE_baseObject,
                ))

            has=o.keys()
            has.sort()
            want=['foo', 'bar']
            want.sort()
            self.assertEquals(has, want)
            self.assertEquals(o['foo'], ['a'])
            self.assertEquals(o['bar'], ['b', 'c'])
        d.addCallback(cb)
        return d

    def testFetch_Prefilled(self):
        """Fetching attributes for a (partially) known object overwrites the old attributes."""
        client = LDAPClientTestDriver(
            [   pureldap.LDAPSearchResultEntry(objectName='cn=foo,dc=example,dc=com',
                                               attributes=(
            ('foo', ['a']),
            ('bar', ['b', 'c']),
            )),
                pureldap.LDAPSearchResultDone(resultCode=0,
                                              matchedDN='',
                                              errorMessage=''),
                ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'foo': ['x'],
            'quux': ['baz', 'xyzzy']
            })
        d=o.fetch()
        def cb(dummy):
            client.assertSent(pureldap.LDAPSearchRequest(
                baseObject='cn=foo,dc=example,dc=com',
                scope=pureldap.LDAP_SCOPE_baseObject,
                ))

            has=o.keys()
            has.sort()
            want=['foo', 'bar']
            want.sort()
            self.assertEquals(has, want)
            self.assertEquals(o['foo'], ['a'])
            self.assertEquals(o['bar'], ['b', 'c'])
        d.addCallback(cb)
        return d

    def testFetch_Partial(self):
        """Fetching only some of the attributes does not overwrite existing values of different attribute types."""
        client = LDAPClientTestDriver(
            [   pureldap.LDAPSearchResultEntry(objectName='cn=foo,dc=example,dc=com',
                                               attributes=(
            ('foo', ['a']),
            ('bar', ['b', 'c']),
            )),
                pureldap.LDAPSearchResultDone(resultCode=0,
                                              matchedDN='',
                                              errorMessage=''),
                ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'foo': ['x'],
            'quux': ['baz', 'xyzzy']
            })
        d=o.fetch('foo', 'bar', 'thud')
        def cb(dummy):
            client.assertSent(pureldap.LDAPSearchRequest(
                baseObject='cn=foo,dc=example,dc=com',
                scope=pureldap.LDAP_SCOPE_baseObject,
                attributes=('foo', 'bar', 'thud'),
                ))

            has=o.keys()
            has.sort()
            want=['foo', 'bar', 'quux']
            want.sort()
            self.assertEquals(has, want)
            self.assertEquals(o['foo'], ['a'])
            self.assertEquals(o['bar'], ['b', 'c'])
            self.assertEquals(o['quux'], ['baz', 'xyzzy'])
        d.addCallback(cb)
        return d

    def testCommitAndFetch(self):
        """Fetching after a commit works."""

        client = LDAPClientTestDriver(
            [   pureldap.LDAPModifyResponse(resultCode=0,
                                            matchedDN='',
                                            errorMessage='')
                ],
            [   pureldap.LDAPSearchResultEntry('cn=foo,dc=example,dc=com',
                                               [('aValue', ['foo', 'bar'])],
                                               ),
                pureldap.LDAPSearchResultDone(resultCode=0),
                ])
        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'aValue': ['a'],
            })

        o['aValue']=['foo', 'bar']
        d=o.commit()
        d.addCallback(self.assertIdentical, o)

        d.addCallback(lambda _: o.fetch('aValue'))
        d.addCallback(self.assertIdentical, o)

        def cb(dummy):
            client.assertSent(delta.ModifyOp('cn=foo,dc=example,dc=com', [
                delta.Replace('aValue', ['foo', 'bar']),
                ]).asLDAP(),
                              pureldap.LDAPSearchRequest(
                baseObject='cn=foo,dc=example,dc=com',
                scope=pureldap.LDAP_SCOPE_baseObject,
                attributes=['aValue'],
                ))
        d.addCallback(cb)
        return d

class LDAPSyntaxRDNHandling(unittest.TestCase):
    def testRemovingRDNFails(self):
        """Removing RDN fails with CannotRemoveRDNError."""
        o=ldapsyntax.LDAPEntry(client=None, dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['someObjectClass'],
            'cn': ['foo', 'bar', 'baz'],
            'a': ['aValue'],
            })
        o['cn'].remove('bar')
        del o['a']
        self.assertRaises(ldapsyntax.CannotRemoveRDNError,
                          o['cn'].remove,
                          'foo')
        def f():
            del o['cn']
        self.assertRaises(ldapsyntax.CannotRemoveRDNError,
                          f)
        def f():
            o['cn']=['thud']
        self.assertRaises(ldapsyntax.CannotRemoveRDNError,
                          f)

        # TODO maybe this should be ok, it preserves the RDN.
        # For now, disallow it.
        def f():
            o['cn']=['foo']
        self.assertRaises(ldapsyntax.CannotRemoveRDNError,
                          f)

class LDAPSyntaxMove(unittest.TestCase):
    def test_move(self):
        client = LDAPClientTestDriver(
            [   pureldap.LDAPModifyDNResponse(resultCode=0,
                                              matchedDN='',
                                              errorMessage=''),
                ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com',
                               attributes={
            'objectClass': ['a', 'b'],
            'cn': ['foo'],
            'aValue': ['a'],
            })
        d = o.move('cn=bar,ou=somewhere,dc=example,dc=com')
        def cb(dummy):
            client.assertSent(pureldap.LDAPModifyDNRequest(
                entry='cn=foo,dc=example,dc=com',
                newrdn='cn=bar',
                deleteoldrdn=1,
                newSuperior='ou=somewhere,dc=example,dc=com',
                ))

            self.assertEquals(o.dn, 'cn=bar,ou=somewhere,dc=example,dc=com')
        d.addCallback(cb)
        return d

class Bind(unittest.TestCase):
    def test_ok(self):
        client = LDAPClientTestDriver(
            [   pureldap.LDAPBindResponse(resultCode=0,
                                          matchedDN=''),
                ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com')
        d = defer.maybeDeferred(o.bind, 's3krit')
        d.addCallback(self.assertIdentical, o)
        def cb(dummy):
            client.assertSent(pureldap.LDAPBindRequest(
                dn='cn=foo,dc=example,dc=com',
                auth='s3krit'))
        d.addCallback(cb)
        return d

    def test_fail(self):
        client = LDAPClientTestDriver(
            [   pureldap.LDAPBindResponse(
            resultCode=ldaperrors.LDAPInvalidCredentials.resultCode,
            matchedDN=''),
                ])

        o=ldapsyntax.LDAPEntry(client=client,
                               dn='cn=foo,dc=example,dc=com')
        d = defer.maybeDeferred(o.bind, 's3krit')
        def eb(fail):
            fail.trap(ldaperrors.LDAPInvalidCredentials)
        d.addCallbacks(testutil.mustRaise, eb)
        return d
