"""
Test cases for ldaptor.protocols.ldap.ldifdelta
"""

from twisted.trial import unittest
from ldaptor.protocols.ldap import ldifdelta
from ldaptor import delta, entry

class LDIFDeltaDriver(ldifdelta.LDIFDelta):
    def __init__(self):
        self.listOfCompleted = []
    def gotEntry(self, obj):
        self.listOfCompleted.append(obj)


"""
changerecord             = "changetype:" FILL
                           (change-add / change-delete /
                            change-modify / change-moddn)

change-add               = "add"                SEP 1*attrval-spec

change-delete            = "delete"             SEP

change-moddn             = ("modrdn" / "moddn") SEP
                            "newrdn:" (    FILL rdn /
                                       ":" FILL base64-rdn) SEP
                            "deleteoldrdn:" FILL ("0" / "1")  SEP
                            0*1("newsuperior:"
                            (    FILL distinguishedName /
                             ":" FILL base64-distinguishedName) SEP)

change-modify            = "modify"             SEP *mod-spec

mod-spec                 = ("add:" / "delete:" / "replace:")
                           FILL AttributeDescription SEP
                           *attrval-spec
                           "-" SEP
"""


"""
version: 1
dn: cn=foo,dc=example,dc=com
changetype: delete

"""

"""
version: 1
dn: cn=foo,dc=example,dc=com
changetype: modrdn  #OR moddn
newrdn: rdn
deleteoldrdn: 0 #OR 1
#0..1 newsuperior: distinguishedName

"""


class TestLDIFDeltaParsing(unittest.TestCase):
    def testModification_empty(self):
        proto = LDIFDeltaDriver()
        proto.dataReceived(b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: modify

""")
        proto.connectionLost()
        self.assertEqual(proto.listOfCompleted,
                         [
            delta.ModifyOp(dn='cn=foo,dc=example,dc=com'),
            ])

    def testModification_oneAdd(self):
        proto = LDIFDeltaDriver()
        proto.dataReceived(b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: modify
add: foo
foo: bar
-

""")
        proto.connectionLost()
        self.assertEqual(
            proto.listOfCompleted,
            [delta.ModifyOp(dn=b'cn=foo,dc=example,dc=com',
                            modifications=[delta.Add(b'foo', [b'bar']),
                                           ]),
             ])

    def testModification_twoAdds(self):
        proto = LDIFDeltaDriver()
        proto.dataReceived(b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: modify
add: foo
foo: bar
-
add: thud
thud: quux
thud: baz
-

""")
        proto.connectionLost()
        self.assertEqual(
            proto.listOfCompleted,
            [delta.ModifyOp(dn=b'cn=foo,dc=example,dc=com',
                            modifications=[delta.Add(b'foo', [b'bar']),
                                           delta.Add(b'thud', [b'quux', b'baz']),
                                           ]),
             ])

    def testModification_complex(self):
        proto = LDIFDeltaDriver()
        proto.dataReceived(b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: modify
delete: foo
foo: bar
-
delete: garply
-
add: thud
thud: quux
thud: baz
-
replace: waldo
-
add: foo
foo: baz
-
replace: thud
thud: xyzzy
-
add: silly
-

""")
        proto.connectionLost()
        self.assertEqual(
            proto.listOfCompleted,
            [delta.ModifyOp(dn=b'cn=foo,dc=example,dc=com',
                            modifications=[delta.Delete(b'foo', [b'bar']),
                                           delta.Delete(b'garply'),
                                           delta.Add(b'thud', [b'quux', b'baz']),
                                           delta.Replace(b'waldo'),
                                           delta.Add(b'foo', [b'baz']),
                                           delta.Replace(b'thud', [b'xyzzy']),
                                           delta.Add(b'silly'),
                                           ]),
             ])

    def testModification_fail_noDash_1(self):
        proto = LDIFDeltaDriver()
        self.assertRaises(ldifdelta.LDIFDeltaModificationMissingEndDashError,
                          proto.dataReceived,
                          b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: modify
add: foo
foo: bar

""")

    def testModification_fail_noDash_2(self):
        proto = LDIFDeltaDriver()
        self.assertRaises(ldifdelta.LDIFDeltaModificationMissingEndDashError,
                          proto.dataReceived,
                          b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: modify
add: foo

""")

    def testModification_fail_differentKey(self):
        proto = LDIFDeltaDriver()
        self.assertRaises(ldifdelta.LDIFDeltaModificationDifferentAttributeTypeError,
                          proto.dataReceived,
                          b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: modify
add: foo
bar: quux
-

""")

    def testModification_fail_unknownModSpec(self):
        proto = LDIFDeltaDriver()
        self.assertRaises(ldifdelta.LDIFDeltaUnknownModificationError,
                          proto.dataReceived,
                          b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: modify
fiddle: foo
foo: bar
-

""")

    def testNoChangeType(self):
        """
        Raises an error is the changetype is not present.
        """
        proto = LDIFDeltaDriver()
        error = self.assertRaises(
            ldifdelta.LDIFDeltaMissingChangeTypeError,
            proto.dataReceived,
            b"""version: 1
dn: cn=foo,dc=example,dc=com
add: foo
foo: bar
-

""")
        self.assertEqual(
            (b'cn=foo,dc=example,dc=com', b'add', b'foo'), error.args)


    def testNoChangetTypeEmpty(self):
        """
        Raises an error is the changetype is not present even if no
        other command are specified for the DN.
        """
        proto = LDIFDeltaDriver()

        error = self.assertRaises(
            ldifdelta.LDIFDeltaMissingChangeTypeError,
            proto.dataReceived,
            b"""version: 1
dn: cn=foo,dc=example,dc=com

""")

        self.assertEqual((b'cn=foo,dc=example,dc=com',), error.args)


    def testAdd(self):
        proto = LDIFDeltaDriver()
        proto.dataReceived(b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: add
foo: bar
thud: quux
thud: baz

""")
        proto.connectionLost()
        self.assertEqual(proto.listOfCompleted,
                         [delta.AddOp(entry.BaseLDAPEntry(
            dn=b'cn=foo,dc=example,dc=com',
            attributes={
            b'foo': [b'bar'],
            b'thud': [b'quux', b'baz'],
            }))])

    def testAdd_fail_noAttrvals(self):
        proto = LDIFDeltaDriver()
        self.assertRaises(ldifdelta.LDIFDeltaAddMissingAttributesError,
                          proto.dataReceived, b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: add

""")


    def testDelete(self):
        """"
        Triggers a DeleteOp when the diff action is `delete`.
        """
        proto = LDIFDeltaDriver()
        proto.dataReceived(b"""\
version: 1
dn: cn=foo,dc=example,dc=com
changetype: delete

""")
        proto.connectionLost()
        self.assertEqual(proto.listOfCompleted,
                         [delta.DeleteOp(dn=b'cn=foo,dc=example,dc=com')])


    def testDeleteMalformat(self):
        """"
        Raises an error when delete change has data after the changetype.
        """
        proto = LDIFDeltaDriver()

        with self.assertRaises(
            ldifdelta.LDIFDeltaDeleteHasJunkAfterChangeTypeError
                ):
            proto.dataReceived(
b"""version: 1
dn: cn=foo,dc=example,dc=com
changetype: delete
foo: bar

""")


    def testMODRDN(self):
        """
        modrdn is not yet supported.
        """
        proto = LDIFDeltaDriver()

        with self.assertRaises(NotImplementedError):
            proto.dataReceived(
b"""version: 1
dn: cn=foo,dc=example,dc=com
changetype: modrdn

""")


    def testMODDN(self):
        """
        moddn is not yet supported.
        """
        proto = LDIFDeltaDriver()

        with self.assertRaises(NotImplementedError):
            proto.dataReceived(
b"""version: 1
dn: cn=foo,dc=example,dc=com
changetype: moddn

""")


    def testUnknownChnagetType(self):
        """
        Raises an error is the changetype is not supported.
        """
        proto = LDIFDeltaDriver()

        with self.assertRaises(ldifdelta.LDIFDeltaUnknownChangeTypeError):
            proto.dataReceived(
b"""version: 1
dn: cn=foo,dc=example,dc=com
changetype: some-random-type

""")

