tests/functional wallace/module_resources.py

Thomas Brüderli bruederli at kolabsys.com
Wed Feb 26 11:48:58 CET 2014


 tests/functional/test_wallace/test_005_resource_invitation.py |   91 ++++++++--
 wallace/module_resources.py                                   |   60 ++++--
 2 files changed, 118 insertions(+), 33 deletions(-)

New commits:
commit 439cea0c64e461546b57e572476fc1b20ac53d54
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Feb 19 23:20:58 2014 -0500

    Fix and test invitation of resource collections and delegation to a (free) collection member

diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index 085ad3b..5bf9767 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -2,6 +2,8 @@ import time
 import pykolab
 import smtplib
 import email
+import datetime
+import uuid
 
 from pykolab import wap_client
 from pykolab.auth import Auth
@@ -43,10 +45,10 @@ PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
 CALSCALE:GREGORIAN
 METHOD:REQUEST
 BEGIN:VEVENT
-UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271
+UID:%s
 DTSTAMP:20120713T1254140
-DTSTART;TZID=Europe/London:20140713T100000
-DTEND;TZID=Europe/London:20140713T160000
+DTSTART;TZID=Europe/London:%s
+DTEND;TZID=Europe/London:%s
 SUMMARY:test
 DESCRIPTION:test
 ORGANIZER;CN="Doe, John":mailto:john.doe at example.org
@@ -76,7 +78,8 @@ class TestResourceInvitation(unittest.TestCase):
         self.john = {
             'displayname': 'John Doe',
             'mail': 'john.doe at example.org',
-            'sender': 'John Doe <john.doe at example.org>'
+            'sender': 'John Doe <john.doe at example.org>',
+            'mailbox': 'user/john.doe at example.org'
         }
 
         from tests.functional.user_add import user_add
@@ -99,14 +102,28 @@ class TestResourceInvitation(unittest.TestCase):
         smtp = smtplib.SMTP('localhost', 10026)
         smtp.sendmail(from_addr, to_addr, msg_source)
 
-    def send_itip_invitation(self, resource_email):
-        self.send_message(itip_invitation % (resource_email, resource_email), resource_email)
+    def send_itip_invitation(self, resource_email, start=None):
+        if start is None:
+            start = datetime.datetime.now()
 
-    def check_message_received(self, subject):
+        uid = str(uuid.uuid4())
+        end = start + datetime.timedelta(hours=4)
+        self.send_message(itip_invitation % (
+                resource_email,
+                uid,
+                start.strftime('%Y%m%dT%H%M%S'),
+                end.strftime('%Y%m%dT%H%M%S'),
+                resource_email
+            ),
+            resource_email)
+
+        return uid
+
+    def check_message_received(self, subject, from_addr=None):
         imap = IMAP()
         imap.connect()
-        imap.set_acl("user/john.doe at example.org", "cyrus-admin", "lrs")
-        imap.imap.m.select("user/john.doe at example.org")
+        imap.set_acl(self.john['mailbox'], "cyrus-admin", "lrs")
+        imap.imap.m.select(self.john['mailbox'])
 
         found = None
         max_tries = 20
@@ -114,7 +131,7 @@ class TestResourceInvitation(unittest.TestCase):
         while not found and max_tries > 0:
             max_tries -= 1
 
-            typ, data = imap.imap.m.search(None, 'ALL')
+            typ, data = imap.imap.m.search(None, '(UNDELETED HEADER FROM "%s")' % (from_addr) if from_addr else 'UNDELETED')
             for num in data[0].split():
                 typ, msg = imap.imap.m.fetch(num, '(RFC822)')
                 message = message_from_string(msg[0][1])
@@ -141,9 +158,12 @@ class TestResourceInvitation(unittest.TestCase):
             typ, data = imap.imap.m.fetch(num, '(RFC822)')
             event_message = message_from_string(data[0][1])
 
+            # return matching UID or first event found
+            if uid and event_message['subject'] != uid:
+                continue
+
             for part in event_message.walk():
-                # return matching UID or first event found
-                if (not uid or event_message['subject'] == uid) and part.get_content_type() == "application/calendar+xml":
+                if part.get_content_type() == "application/calendar+xml":
                     payload = part.get_payload(decode=True)
                     found = pykolab.xml.event_from_string(payload)
                     break
@@ -155,6 +175,19 @@ class TestResourceInvitation(unittest.TestCase):
 
         return found
 
+    def purge_mailbox(self, mailbox):
+        imap = IMAP()
+        imap.connect()
+        imap.set_acl(mailbox, "cyrus-admin", "lrwcdest")
+        imap.imap.m.select(u'"'+mailbox+'"')
+
+        typ, data = imap.imap.m.search(None, 'ALL')
+        for num in data[0].split():
+            imap.imap.m.store(num, '+FLAGS', '\\Deleted')
+
+        imap.imap.m.expunge()
+        imap.disconnect()
+
 
     def test_001_resource_from_email_address(self):
         resource = module_resources.resource_record_from_email_address(self.audi['mail'])
@@ -167,11 +200,39 @@ class TestResourceInvitation(unittest.TestCase):
 
 
     def test_002_invite_resource(self):
-        self.send_itip_invitation(self.audi['mail'])
+        uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,7,13, 10,0,0))
 
-        response = self.check_message_received("Meeting Request ACCEPTED")
+        response = self.check_message_received("Meeting Request ACCEPTED", self.audi['mail'])
         self.assertIsInstance(response, email.message.Message)
 
-        event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], '626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271')
+        event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
         self.assertIsInstance(event, pykolab.xml.Event)
         self.assertEqual(event.get_summary(), "test")
+
+
+    def test_003_invite_resource_conflict(self):
+        uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,7,13, 12,0,0))
+
+        response = self.check_message_received("Meeting Request DECLINED", self.audi['mail'])
+        self.assertIsInstance(response, email.message.Message)
+
+        self.assertEqual(self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid), None)
+
+
+    def test_004_invite_resource_collection(self):
+        self.purge_mailbox(self.john['mailbox'])
+
+        uid = self.send_itip_invitation(self.cars['mail'], datetime.datetime(2014,7,13, 12,0,0))
+
+        # one of the collection members accepted the reservation
+        accept = self.check_message_received("Meeting Request ACCEPTED")
+        self.assertIsInstance(accept, email.message.Message)
+        self.assertIn(accept['from'], [ self.passat['mail'], self.boxter['mail'] ])
+
+        # check booking in the delegatee's resource calendar
+        delegatee = self.passat if accept['from'] == self.passat['mail'] else self.boxter
+        self.assertIsInstance(self.check_resource_calendar_event(delegatee['kolabtargetfolder'], uid), pykolab.xml.Event)
+
+        # resource collection respons with a DELEGATED message
+        response = self.check_message_received("Meeting Request DELEGATED", self.cars['mail'])
+        self.assertIsInstance(response, email.message.Message)
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index e76df24..eaa547b 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -187,19 +187,19 @@ def execute(*args, **kw):
 
     # A simple list of merely resource entry IDs that hold any relevance to the
     # iTip events
-    resource_records = resource_records_from_itip_events(itip_events, resource_recipient)
+    resource_dns = resource_records_from_itip_events(itip_events, resource_recipient)
 
     # Get the resource details, which includes details on the IMAP folder
     resources = {}
-    for resource_record in list(set(resource_records)):
+    for resource_dn in list(set(resource_dns)):
         # Get the attributes for the record
         # See if it is a resource collection
         #   If it is, expand to individual resources
         #   If it is not, ...
-        resource_attrs = auth.get_entry_attributes(None, resource_record, ['*'])
+        resource_attrs = auth.get_entry_attributes(None, resource_dn, ['*'])
         if not 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
             if resource_attrs.has_key('uniquemember'):
-                resources[resource_record] = resource_attrs
+                resources[resource_dn] = resource_attrs
                 for uniquemember in resource_attrs['uniquemember']:
                     resource_attrs = auth.get_entry_attributes(
                             None,
@@ -209,11 +209,12 @@ def execute(*args, **kw):
 
                     if 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
                         resources[uniquemember] = resource_attrs
-                        resources[uniquemember]['memberof'] = resource_record
+                        resources[uniquemember]['memberof'] = resource_dn
+                        resource_dns.append(uniquemember)
         else:
-            resources[resource_record] = resource_attrs
+            resources[resource_dn] = resource_attrs
 
-    log.debug(_("Resources: %r") % (resources), level=8)
+    log.debug(_("Resources: %r, %r") % (resource_dns, resources), level=8)
 
     # For each resource, determine if any of the events in question is in
     # conflict.
@@ -239,21 +240,39 @@ def execute(*args, **kw):
 
     done = False
 
-    for resource in resources.keys():
+    for resource in resource_dns:
         log.debug(_("Polling for resource %r") % (resource), level=9)
 
-        if done:
-            break
-
         if not resources.has_key(resource):
             log.debug(_("Resource %r has been popped from the list") % (resource), level=9)
             continue
 
         if not resources[resource].has_key('conflicting_events'):
             log.debug(_("Resource is a collection"), level=9)
+
+            # check if there are non-conflicting collection members
+            conflicting_members = [x for x in resources[resource]['uniquemember'] if resources[x]['conflict']]
+
+            # found at least one non-conflicting member, remove the conflicting ones and continue
+            if len(conflicting_members) < len(resources[resource]['uniquemember']):
+                for member in conflicting_members:
+                    resources[resource]['uniquemember'] = [x for x in resources[resource]['uniquemember'] if x != member]
+                    del resources[member]
+
+                log.debug(_("Removed conflicting resources from %r: (%r) => %r") % (
+                    resource, conflicting_members, resources[resource]['uniquemember']
+                ), level=9)
+
+            else:
+                # TODO: shuffle existing bookings of collection members in order
+                # to make one availale for the requested time
+                pass
+
             continue
 
         if len(resources[resource]['conflicting_events']) > 0:
+            log.debug(_("Conflicting events: %r for resource %r") % (resources[resource]['conflicting_events'], resource), level=9)
+
             # This is the event being conflicted with!
             for itip_event in itip_events:
                 # Now we have the event that was conflicting
@@ -268,9 +287,6 @@ def execute(*args, **kw):
                     if resources[resource].has_key('memberof'):
                         original_resource = resources[resources[resource]['memberof']]
 
-                        # TODO: shuffle existing bookings of collection members in order
-                        # to make one availale for the requested time
-
                     if original_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
                         decline_reservation_request(itip_event, original_resource)
                         done = True
@@ -279,6 +295,7 @@ def execute(*args, **kw):
             # No conflicts, go accept
             for itip_event in itip_events:
                 if resources[resource]['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
+                    log.debug(_("Accept invitation for individual resource %r / %r") % (resource, resources[resource]['mail']), level=9)
                     accept_reservation_request(itip_event, resources[resource], imap)
                     done = True
 
@@ -292,6 +309,8 @@ def execute(*args, **kw):
                         # Randomly selects a target resource from the resource collection.
                         _target_resource = resources[original_resource['uniquemember'][random.randint(0,(len(original_resource['uniquemember'])-1))]]
 
+                        log.debug(_("Delegate invitation for resource collection %r to %r") % (original_resource['mail'], _target_resource['mail']), level=9)
+
                     if original_resource['mail'] in [a.get_email() for a in itip_event['xml'].get_attendees()]:
                         #
                         # Delegate:
@@ -301,9 +320,14 @@ def execute(*args, **kw):
                         #
                         itip_event['xml'].delegate(original_resource['mail'], _target_resource['mail'])
 
-                        accept_reservation_request(itip_event, _target_resource, imap)
+                        accept_reservation_request(itip_event, _target_resource, imap, delegator=original_resource)
                         done = True
 
+        if done:
+            break
+
+        # for resource in resource_dns:
+
     auth.disconnect()
     del auth
 
@@ -391,7 +415,7 @@ def read_resource_calendar(resource_rec, itip_events, imap):
     return resource_rec['conflict']
 
 
-def accept_reservation_request(itip_event, resource, imap):
+def accept_reservation_request(itip_event, resource, imap, delegator=None):
     """
         Accepts the given iTip event by booking it into the resource's
         calendar. Then set the attendee status of the given resource to
@@ -418,7 +442,7 @@ def accept_reservation_request(itip_event, resource, imap):
             itip_event['xml'].to_message().as_string()
         )
 
-    send_response(resource['mail'], itip_event)
+    send_response(delegator['mail'] if delegator else resource['mail'], itip_event)
 
 
 def decline_reservation_request(itip_event, resource):
@@ -432,7 +456,7 @@ def decline_reservation_request(itip_event, resource):
         "DECLINED"
     )
 
-    send_response(resource, itip_event)
+    send_response(resource['mail'], itip_event)
 
 
 def itip_events_from_message(message):




More information about the commits mailing list