# Copyright (C) 2008 by Intevation GmbH
# Authors:
# Bernhard Herzog <bh@intevation.de>
#
# This program is free software under the LGPL (>=v2.1)
# Read the file COPYING coming with the software for details.

import unittest
from imapsupport import ImapConnection, ImapError, default_config


class AnnotationTests(unittest.TestCase):

    def setUp(self):
        self.config = default_config()
        self.imap = ImapConnection(self.config.host, self.config.port,
                                   self.config.login, self.config.password,
                                   self.config.ssl)

    def tearDown(self):
        self.imap.logout()

    def ensure_mailbox_unknown(self, mailbox):
        try:
            self.imap.delete(mailbox)
        except ImapError:
            pass

    def ensure_mailbox_new_and_empty(self, mailbox):
        self.ensure_mailbox_unknown(mailbox)
        self.imap.create(mailbox)

    def check_bad_parameters(self, command, args, message_template):
        try:
            getattr(self.imap, command.lower())(*args)
        except self.imap.error_class, e:
            self.failUnless(str(e).startswith("%s command error: BAD"
                                              % command.upper()))
        else:
            self.fail(message_template % command.upper())

    def check_imap_error(self, command, args, message_template,
                         response_type="NO", response=None):
        try:
            getattr(self.imap, command.lower())(*args)
        except ImapError, e:
            self.failUnless(e.imap_result[0] == response_type)
            if response:
                self.failUnless(response in e.imap_result[1])
        else:
            self.fail(message_template % command.upper())


class TestCapabilities(AnnotationTests):

    def test_capabilities(self):
        self.failUnless("ANNOTATEMORE" in self.imap.capabilities)


class TestGetAnnotation(AnnotationTests):

    def setUp(self):
        AnnotationTests.setUp(self)
        self.mailbox = "INBOX"
        self.entry = "/vendor/kolab/folder-type"
        self.attribute = "value.shared"

    def test_getannotation_no_parameters(self):
        self.check_bad_parameters("GETANNOTATION", (),
                                  "%s without parameters did not return an"
                                  " error")

    def test_getannotation_one_parameters(self):
        self.check_bad_parameters("GETANNOTATION", ("INBOX",),
                                  "%s with only one parameter did not return an"
                                  " error")

    def test_getannotation_two_parameters(self):
        self.check_bad_parameters("GETANNOTATION",
                                  ("INBOX", "/vendor/kolab/test"),
                                  "%s with two parameters did not return an"
                                  " error")

    def test_getannotation_entry_no_slash(self):
        self.check_bad_parameters("GETANNOTATION",
                                  ("INBOX", "comment", "value.shared"),
                                  "%s with entry not starting with slash"
                                  " did not return an error")

    def test_getannotation_attribute_nil(self):
        self.check_bad_parameters("GETANNOTATION",
                                  (self.mailbox, self.entry, None),
                                  "%s with attribute name NIL did not return"
                                  " an error")

    def test_getannotation_nonexisting_mailbox(self):
        mailbox = "INBOX/nowhere"
        # make sure mailbox doesn't exist
        self.ensure_mailbox_unknown(mailbox)
        self.check_imap_error("GETANNOTATION",
                              (mailbox, "/comment", "value.shared"),
                              "%s for non-existing mailbox did not"
                              " return an error")

    def test_getannotation_unknown_namespace(self):
        mailbox = "unknown/foo"
        # make sure mailbox doesn't exist
        self.ensure_mailbox_unknown(mailbox)
        self.check_imap_error("GETANNOTATION",
                              (mailbox, "/comment", ("value.shared", "foo")),
                              "%s for unknown mailbox namespace did not"
                              " return an error")

    def test_getannotation(self):
        annotation = self.imap.getannotation("INBOX", "/vendor/kolab/test",
                                             "value.shared")
        self.assertEquals(annotation, [])

    def test_getannotation_lists_wuth_single_elements(self):
        mailbox = "INBOX/annotated"
        entry = "/vendor/kolab/test"
        self.ensure_mailbox_new_and_empty(mailbox)
        self.imap.setannotation(mailbox, entry,
                                ["value.shared", "an annotation"])
        annotation = self.imap.getannotation(mailbox, [entry], ["value.shared"])
        self.assertEquals(annotation,
                          ['"INBOX/annotated" "/vendor/kolab/test"'
                           ' ("value.shared" "an annotation")'])


class TestSetAnnotation(AnnotationTests):

    def setUp(self):
        AnnotationTests.setUp(self)
        self.mailbox = "INBOX/Calendar"
        self.entry = "/vendor/kolab/folder-type"
        self.attribute = "value.shared"
        self.value = "event"
        self.ensure_mailbox_new_and_empty(self.mailbox)

    def test_setannotation_no_argument(self):
        self.check_bad_parameters("SETANNOTATION", (),
                                  "%s with no parameters did not return an"
                                  " error")

    def test_setannotation_one_argument(self):
        self.check_bad_parameters("SETANNOTATION", (self.mailbox),
                                  "%s with one parameter did not return an"
                                  " error")

    def test_setannotation_two_argument(self):
        self.check_bad_parameters("SETANNOTATION", (self.mailbox, self.entry),
                                  "%s with two parameters did not return an"
                                  " error")

    def test_setannotation_entry_no_slash(self):
        self.check_bad_parameters("SETANNOTATION",
                                  (self.mailbox, "comment",
                                   [self.attribute, self.value]),
                                  "%s with entry not starting with slash"
                                  " did not return an error")

    def test_setannotation_attribute_no_list(self):
        self.check_bad_parameters("SETANNOTATION",
                                  (self.mailbox, self.entry, self.attribute),
                                  "%s with two parameters did not return an"
                                  " error")

    def test_setannotation_attribute_odd_list(self):
        self.check_bad_parameters("SETANNOTATION",
                                  (self.mailbox, self.entry,
                                   ["folder-type", "mail", "entry"]),
                                  "%s with two parameters did not return an"
                                  " error")

    def test_setannotation_attribute_empty_list(self):
        self.check_bad_parameters("SETANNOTATION",
                                  (self.mailbox, self.entry, []),
                                  "%s with empty attribute list did not return"
                                  " an error")

    def test_setannotation_attribute_nil(self):
        self.check_bad_parameters("SETANNOTATION",
                                  (self.mailbox, self.entry, [None, "event"]),
                                  "%s with attribute name NIL did not return"
                                  " an error")

    def test_setannotation_attribute_without_period(self):
        self.check_bad_parameters("SETANNOTATION",
                                  (self.mailbox, self.entry,
                                   ["folder-type", "mail"]),
                                  "%s with attribute without period did not"
                                  " return an error")

    def test_setannotation_attribute_not_ending_in_priv_or_shared(self):
        self.check_bad_parameters("SETANNOTATION",
                                  (self.mailbox, self.entry,
                                   ["value.type", "mail"]),
                                  "%s with attribute not ending in priv or"
                                  " shared did not return an error")

    def test_setannotation_nonexisting_mailbox(self):
        mailbox = "INBOX/nowhere"
        self.ensure_mailbox_unknown(mailbox)
        self.check_imap_error("SETANNOTATION",
                              (mailbox, "/comment", ("value.shared", "foo")),
                              "%s for non-existing mailbox did not"
                              " return an error")

    def test_setannotation_unknown_namespace(self):
        mailbox = "unknown/foo"
        self.ensure_mailbox_unknown(mailbox)
        self.check_imap_error("SETANNOTATION",
                              (mailbox, "/comment", ("value.shared", "foo")),
                              "%s for unknown mailbox namespace did not"
                              " return an error")

    def test_setannotation_public(self):
        self.imap.setannotation(self.mailbox, self.entry,
                                [self.attribute, self.value])
        annotation = self.imap.getannotation(self.mailbox, self.entry,
                                             self.attribute)
        self.assertEquals(annotation,
                          ['"%s" "%s" ("%s" "%s")'
                           % (self.mailbox, self.entry,
                              self.attribute, self.value)])

    def test_setannotation_deleting_public(self):
        self.imap.setannotation(self.mailbox, self.entry,
                                [self.attribute, self.value])
        self.imap.setannotation(self.mailbox, self.entry,
                                [self.attribute, None])
        self.assertEquals(self.imap.getannotation(self.mailbox, self.entry,
                                                  self.attribute),
                          [])


class TestMailboxChanges(AnnotationTests):

    def test_mailbox_renaming(self):
        self.ensure_mailbox_unknown("INBOX/old")
        self.ensure_mailbox_unknown("INBOX/new")

        self.imap.create("INBOX/old")
        self.imap.setannotation("INBOX/old", "/vendor/kolab/folder-test",
                                ["value.shared", "some value"])
        self.imap.rename("INBOX/old", "INBOX/new")

        self.assertEquals(self.imap.getannotation("INBOX/new",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          ['"INBOX/new" "/vendor/kolab/folder-test"'
                           ' ("value.shared" "some value")'])

    def test_mailbox_renaming_with_inferior_mailboxes(self):
        self.ensure_mailbox_new_and_empty("INBOX/this")
        self.ensure_mailbox_new_and_empty("INBOX/this/child")
        self.ensure_mailbox_new_and_empty("INBOX/this/child/grandchild")
        self.ensure_mailbox_unknown("INBOX/that")
        self.ensure_mailbox_unknown("INBOX/that/child")
        self.ensure_mailbox_unknown("INBOX/that/child/grandchild")

        self.imap.setannotation("INBOX/this/child",
                                "/vendor/kolab/folder-test",
                                ["value.shared", "child value"])
        self.imap.setannotation("INBOX/this/child/grandchild",
                                "/vendor/kolab/folder-test",
                                ["value.shared", "grand child value"])
        self.imap.rename("INBOX/this", "INBOX/that")

        self.assertEquals(self.imap.getannotation("INBOX/that/child",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          ['"INBOX/that/child" "/vendor/kolab/folder-test"'
                           ' ("value.shared" "child value")'])
        self.assertEquals(self.imap.getannotation("INBOX/that/child/grandchild",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          ['"INBOX/that/child/grandchild"'
                           ' "/vendor/kolab/folder-test"'
                           ' ("value.shared" "grand child value")'])

    def test_mailbox_renaming_with_common_prefixes(self):
        self.ensure_mailbox_new_and_empty("INBOX/this")
        self.ensure_mailbox_new_and_empty("INBOX/this/child")
        self.ensure_mailbox_new_and_empty("INBOX/thisother")
        self.ensure_mailbox_new_and_empty("INBOX/thisother/child")
        self.ensure_mailbox_unknown("INBOX/that")
        self.ensure_mailbox_unknown("INBOX/that/child")

        self.imap.setannotation("INBOX/this/child",
                                "/vendor/kolab/folder-test",
                                ["value.shared", "this child value"])
        self.imap.setannotation("INBOX/thisother/child",
                                "/vendor/kolab/folder-test",
                                ["value.shared", "other child value"])
        self.imap.rename("INBOX/this", "INBOX/that")

        self.assertEquals(self.imap.getannotation("INBOX/that/child",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          ['"INBOX/that/child" "/vendor/kolab/folder-test"'
                           ' ("value.shared" "this child value")'])
        self.assertEquals(self.imap.getannotation("INBOX/thisother/child",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          ['"INBOX/thisother/child"'
                           ' "/vendor/kolab/folder-test"'
                           ' ("value.shared" "other child value")'])

    def test_mailbox_deletion(self):
        self.ensure_mailbox_new_and_empty("INBOX/deletiontest")
        self.imap.setannotation("INBOX/deletiontest",
                                "/vendor/kolab/folder-test",
                                ["value.shared", "some value"])
        self.assertEquals(self.imap.getannotation("INBOX/deletiontest",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          ['"INBOX/deletiontest" "/vendor/kolab/folder-test"'
                           ' ("value.shared" "some value")'])
        self.imap.delete("INBOX/deletiontest")
        self.imap.create("INBOX/deletiontest")
        self.assertEquals(self.imap.getannotation("INBOX/deletiontest",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          [])

    def test_mailbox_deletion_with_common_prefixes(self):
        self.ensure_mailbox_new_and_empty("INBOX/deletiontest")
        self.ensure_mailbox_new_and_empty("INBOX/deletiontestother")

        self.imap.setannotation("INBOX/deletiontest",
                                "/vendor/kolab/folder-test",
                                ["value.shared", "some value"])
        self.imap.setannotation("INBOX/deletiontestother",
                                "/vendor/kolab/folder-test",
                                ["value.shared", "other value"])
        self.imap.delete("INBOX/deletiontest")
        self.imap.create("INBOX/deletiontest")
        self.assertEquals(self.imap.getannotation("INBOX/deletiontest",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          [])
        self.assertEquals(self.imap.getannotation("INBOX/deletiontestother",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          ['"INBOX/deletiontestother"'
                           ' "/vendor/kolab/folder-test"'
                           ' ("value.shared" "other value")'])

    def test_mailbox_deletion_with_inferior_mailboxes(self):
        self.ensure_mailbox_new_and_empty("INBOX/deletion")
        self.ensure_mailbox_new_and_empty("INBOX/deletion/child")

        self.imap.setannotation("INBOX/deletion/child",
                                "/vendor/kolab/folder-test",
                                ["value.shared", "child value"])
        self.imap.delete("INBOX/deletion")
        self.assertEquals(self.imap.getannotation("INBOX/deletion/child",
                                                  "/vendor/kolab/folder-test",
                                                  "value.shared"),
                          ['"INBOX/deletion/child" "/vendor/kolab/folder-test"'
                           ' ("value.shared" "child value")'])

    def test_renaming_and_deletion_with_large_datasets(self):
        self.ensure_mailbox_new_and_empty("INBOX/many-annotations")
        self.ensure_mailbox_unknown("INBOX/many-renamed")

        value = "x" * 1024
        for i in range(1024):
            entry = "/public/vendor/kolab/numbered_%03d" % i
            self.imap.setannotation("INBOX/many-annotations",
                                    entry, ["value.shared", value])

        self.imap.rename("INBOX/many-annotations", "INBOX/many-renamed")

        for i in range(1024):
            entry = "/public/vendor/kolab/numbered_%03d" % i
            self.assertEquals(self.imap.getannotation("INBOX/many-renamed",
                                                      entry, "value.shared"),
                              ['"INBOX/many-renamed" "%s" ("value.shared" "%s")'
                               % (entry, value)])

        self.imap.delete("INBOX/many-renamed")



if __name__ == "__main__":
    unittest.main()
