3 commits - pykolab/xml tests/functional tests/unit wallace/module_invitationpolicy.py wallace/module_resources.py

Thomas Brüderli bruederli at kolabsys.com
Fri Jul 18 15:26:37 CEST 2014


 pykolab/xml/__init__.py                                       |    1 
 pykolab/xml/event.py                                          |   73 ++++
 tests/functional/test_wallace/test_005_resource_invitation.py |   18 -
 tests/functional/test_wallace/test_007_invitationpolicy.py    |   27 +
 tests/unit/test-003-event.py                                  |  162 ++++++----
 tests/unit/test-012-wallace_invitationpolicy.py               |    2 
 wallace/module_invitationpolicy.py                            |   43 +-
 wallace/module_resources.py                                   |   39 +-
 8 files changed, 250 insertions(+), 115 deletions(-)

New commits:
commit a674289628249f45ec46c3096b1c476692943ff3
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Jul 7 18:43:25 2014 -0400

    Use new pykolab.xml.event_from_message() function in wallace modules and verify that attachments survive event updates

diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index 946cb5f..d9f2d41 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -8,6 +8,7 @@ import uuid
 from pykolab.imap import IMAP
 from wallace import module_resources
 
+from pykolab.xml import event_from_message
 from email import message_from_string
 from twisted.trial import unittest
 
@@ -23,7 +24,7 @@ CALSCALE:GREGORIAN
 METHOD:REQUEST
 BEGIN:VEVENT
 UID:%s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
 DTSTART;TZID=Europe/London:%s
 DTEND;TZID=Europe/London:%s
 SUMMARY:test
@@ -43,7 +44,7 @@ CALSCALE:GREGORIAN
 METHOD:REQUEST
 BEGIN:VEVENT
 UID:%s
-DTSTAMP:20140215T1254140
+DTSTAMP:20140215T125414Z
 DTSTART;TZID=Europe/London:%s
 DTEND;TZID=Europe/London:%s
 SEQUENCE:2
@@ -90,7 +91,7 @@ CALSCALE:GREGORIAN
 METHOD:CANCEL
 BEGIN:VEVENT
 UID:%s
-DTSTAMP:20140218T1254140
+DTSTAMP:20140218T125414Z
 DTSTART;TZID=Europe/London:20120713T100000
 DTEND;TZID=Europe/London:20120713T110000
 SUMMARY:test
@@ -112,7 +113,7 @@ CALSCALE:GREGORIAN
 METHOD:REQUEST
 BEGIN:VEVENT
 UID:%s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
 DTSTART;VALUE=DATE:%s
 DTEND;VALUE=DATE:%s
 SUMMARY:test
@@ -133,7 +134,7 @@ CALSCALE:GREGORIAN
 METHOD:REQUEST
 BEGIN:VEVENT
 UID:%s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
 DTSTART;TZID=Europe/Zurich:%s
 DTEND;TZID=Europe/Zurich:%s
 RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10
@@ -328,12 +329,7 @@ class TestResourceInvitation(unittest.TestCase):
                 if uid and event_message['subject'] != uid:
                     continue
 
-                for part in event_message.walk():
-                    if part.get_content_type() == "application/calendar+xml":
-                        payload = part.get_payload(decode=True)
-                        found = pykolab.xml.event_from_string(payload)
-                        break
-
+                found = event_from_message(event_message)
                 if found:
                     break
 
diff --git a/tests/functional/test_wallace/test_007_invitationpolicy.py b/tests/functional/test_wallace/test_007_invitationpolicy.py
index 4fd61a7..3b68aef 100644
--- a/tests/functional/test_wallace/test_007_invitationpolicy.py
+++ b/tests/functional/test_wallace/test_007_invitationpolicy.py
@@ -11,6 +11,7 @@ from pykolab.imap import IMAP
 from wallace import module_resources
 
 from pykolab.translate import _
+from pykolab.xml import event_from_message
 from email import message_from_string
 from twisted.trial import unittest
 
@@ -26,7 +27,7 @@ CALSCALE:GREGORIAN
 METHOD:REQUEST
 BEGIN:VEVENT
 UID:%(uid)s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
 DTSTART;TZID=Europe/Berlin:%(start)s
 DTEND;TZID=Europe/Berlin:%(end)s
 SUMMARY:%(summary)s
@@ -48,7 +49,7 @@ CALSCALE:GREGORIAN
 METHOD:CANCEL
 BEGIN:VEVENT
 UID:%(uid)s
-DTSTAMP:20140218T1254140
+DTSTAMP:20140218T125414Z
 DTSTART;TZID=Europe/Berlin:20120713T100000
 DTEND;TZID=Europe/Berlin:20120713T110000
 SUMMARY:%(summary)s
@@ -69,7 +70,7 @@ CALSCALE:GREGORIAN
 METHOD:REQUEST
 BEGIN:VEVENT
 UID:%(uid)s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
 DTSTART;TZID=Europe/Zurich:%(start)s
 DTEND;TZID=Europe/Zurich:%(end)s
 RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10
@@ -295,6 +296,14 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         event.set_summary(summary)
         event.set_sequence(sequence)
 
+        # create event with attachment
+        vattach = event.get_attachments()
+        attachment = kolabformat.Attachment()
+        attachment.setLabel('attach.txt')
+        attachment.setData('This is a text attachment', 'text/plain')
+        vattach.append(attachment)
+        event.event.setAttachments(vattach)
+
         imap = IMAP()
         imap.connect()
 
@@ -365,12 +374,7 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
                 if uid and event_message['subject'] != uid:
                     continue
 
-                for part in event_message.walk():
-                    if part.get_content_type() == "application/calendar+xml":
-                        payload = part.get_payload(decode=True)
-                        found = pykolab.xml.event_from_string(payload)
-                        break
-
+                found = event_from_message(event_message)
                 if found:
                     break
 
@@ -506,6 +510,11 @@ class TestWallaceInvitationpolicy(unittest.TestCase):
         self.assertIsInstance(attendee, pykolab.xml.Attendee)
         self.assertEqual(attendee.get_participant_status(), kolabformat.PartAccepted)
 
+        # check attachments in update event
+        attachments = event.get_attachments()
+        self.assertEqual(len(attachments), 1)
+        self.assertEqual(event.get_attachment_data(0), 'This is a text attachment')
+
 
     def test_007_invitation_cancel(self):
         self.purge_mailbox(self.john['mailbox'])
diff --git a/wallace/module_invitationpolicy.py b/wallace/module_invitationpolicy.py
index a141251..7375d2d 100644
--- a/wallace/module_invitationpolicy.py
+++ b/wallace/module_invitationpolicy.py
@@ -39,7 +39,7 @@ from pykolab.auth import Auth
 from pykolab.conf import Conf
 from pykolab.imap import IMAP
 from pykolab.xml import to_dt
-from pykolab.xml import event_from_string
+from pykolab.xml import event_from_message
 from pykolab.itip import events_from_message
 from pykolab.itip import check_event_conflict
 from pykolab.itip import send_reply
@@ -619,15 +619,12 @@ def find_existing_event(itip_event, user_rec):
         for num in reversed(data[0].split()):
             typ, data = imap.imap.m.fetch(num, '(RFC822)')
 
-            event_message = message_from_string(data[0][1])
-
-            if event_message.is_multipart():
-                for part in event_message.walk():
-                    if part.get_content_type() == "application/calendar+xml":
-                        payload = part.get_payload(decode=True)
-                        event = event_from_string(payload)
-                        setattr(event, '_imap_folder', folder)
-                        break
+            try:
+                event = event_from_message(message_from_string(data[0][1]))
+                setattr(event, '_imap_folder', folder)
+            except Exception, e:
+                log.error(_("Failed to parse event from message %s/%s: %r") % (folder, num, e))
+                continue
 
             if event and event.uid == itip_event['uid']:
                 return event
@@ -659,20 +656,18 @@ def check_availability(itip_event, receiving_user):
             event = None
             typ, data = imap.imap.m.fetch(num, '(RFC822)')
 
-            event_message = message_from_string(data[0][1])
-
-            if event_message.is_multipart():
-                for part in event_message.walk():
-                    if part.get_content_type() == "application/calendar+xml":
-                        payload = part.get_payload(decode=True)
-                        event = event_from_string(payload)
-                        break
-
-                if event and event.uid:
-                    conflict = check_event_conflict(event, itip_event)
-                    if conflict:
-                        log.info(_("Existing event %r conflicts with invitation %r") % (event.uid, itip_event['uid']))
-                        break
+            try:
+                event = event_from_message(message_from_string(data[0][1]))
+                setattr(event, '_imap_folder', folder)
+            except Exception, e:
+                log.error(_("Failed to parse event from message %s/%s: %r") % (folder, num, e))
+                continue
+
+            if event and event.uid:
+                conflict = check_event_conflict(event, itip_event)
+                if conflict:
+                    log.info(_("Existing event %r conflicts with invitation %r") % (event.uid, itip_event['uid']))
+                    break
 
         if conflict:
             break
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index f398120..3864f7c 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -40,8 +40,8 @@ import kolabformat
 from pykolab.auth import Auth
 from pykolab.conf import Conf
 from pykolab.imap import IMAP
-from pykolab.xml import event_from_string
 from pykolab.xml import to_dt
+from pykolab.xml import event_from_message
 from pykolab.itip import events_from_message
 from pykolab.itip import check_event_conflict
 from pykolab.translate import _
@@ -467,28 +467,29 @@ def read_resource_calendar(resource_rec, itip_events):
 
         event_message = message_from_string(data[0][1])
 
-        if event_message.is_multipart():
-            for part in event_message.walk():
-                if part.get_content_type() == "application/calendar+xml":
-                    payload = part.get_payload(decode=True)
-                    event = pykolab.xml.event_from_string(payload)
+        try:
+            event = event_from_message(message_from_string(data[0][1]))
+        except Exception, e:
+            log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e))
+            continue
 
-                    for itip in itip_events:
-                        conflict = check_event_conflict(event, itip)
+        if event:
+            for itip in itip_events:
+                conflict = check_event_conflict(event, itip)
 
-                        if event.get_uid() == itip['uid']:
-                            resource_rec['existing_events'].append(itip['uid'])
+                if event.get_uid() == itip['uid']:
+                    resource_rec['existing_events'].append(itip['uid'])
 
-                        if conflict:
-                            log.info(
-                                _("Event %r conflicts with event %r") % (
-                                    itip['xml'].get_uid(),
-                                    event.get_uid()
-                                )
-                            )
+                if conflict:
+                    log.info(
+                        _("Event %r conflicts with event %r") % (
+                            itip['xml'].get_uid(),
+                            event.get_uid()
+                        )
+                    )
 
-                            resource_rec['conflicting_events'].append(event.get_uid())
-                            resource_rec['conflict'] = True
+                    resource_rec['conflicting_events'].append(event.get_uid())
+                    resource_rec['conflict'] = True
 
     return num_messages
 


commit 6e137a89f6f29975133633e2e70557b3954d4220
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Jul 7 18:29:00 2014 -0400

    Add getter for event attachment data

diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 39034f6..f9b6487 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -303,6 +303,22 @@ class Event(object):
     def get_attachments(self):
         return self.event.attachments()
 
+    def get_attachment_data(self, i):
+        vattach = self.event.attachments()
+        if i < len(vattach):
+            attachment = vattach[i]
+            uri = attachment.uri()
+            if uri and uri[0:4] == 'cid:':
+                # get data from MIME part with matching content-id
+                cid = '<' + uri[4:] + '>'
+                for p in self._attachment_parts:
+                    if p['Content-ID'] == cid:
+                        return p.get_payload(decode=True)
+            else:
+                return attachment.data()
+
+        return None
+
     def get_alarms(self):
         return self.event.alarms()
 
diff --git a/tests/unit/test-003-event.py b/tests/unit/test-003-event.py
index 363c75e..81337d9 100644
--- a/tests/unit/test-003-event.py
+++ b/tests/unit/test-003-event.py
@@ -389,6 +389,7 @@ METHOD:REQUEST
         self.assertEqual(parts[3]['Content-ID'].strip('<>'), attachments[0].uri()[4:])
         self.assertEqual(parts[4].get_content_type(), 'text/plain')
         self.assertEqual(parts[4]['Content-ID'].strip('<>'), attachments[1].uri()[4:])
+        self.assertEqual(event.get_attachment_data(1), 'This is a text file')
 
     def test_024_bogus_itip_data(self):
         # DTSTAMP contains an invalid date/time value


commit 5de26915e7b66db1dd139b1b6d33687f7e9dfc66
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Jul 7 17:40:54 2014 -0400

    Fix event object reading and writing: store attachments as separate MIME parts and forward them to the new MIME message written

diff --git a/pykolab/xml/__init__.py b/pykolab/xml/__init__.py
index 5ca2837..64b06ae 100644
--- a/pykolab/xml/__init__.py
+++ b/pykolab/xml/__init__.py
@@ -9,6 +9,7 @@ from event import EventIntegrityError
 from event import InvalidEventDateError
 from event import event_from_ical
 from event import event_from_string
+from event import event_from_message
 
 from utils import to_dt
 
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index fcb3a17..39034f6 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -6,6 +6,7 @@ import kolabformat
 import pytz
 import time
 import uuid
+import base64
 
 import pykolab
 from pykolab import constants
@@ -13,6 +14,7 @@ from pykolab import utils
 from pykolab.xml import utils as xmlutils
 from pykolab.translate import _
 
+from os import path
 from attendee import Attendee
 from contact_reference import ContactReference
 
@@ -24,6 +26,21 @@ def event_from_ical(string):
 def event_from_string(string):
     return Event(from_string=string)
 
+def event_from_message(message):
+    event = None
+    if message.is_multipart():
+        for part in message.walk():
+            if part.get_content_type() == "application/calendar+xml":
+                payload = part.get_payload(decode=True)
+                event = event_from_string(payload)
+
+            # append attachment parts to Event object
+            elif event and part.has_key('Content-ID'):
+                event._attachment_parts.append(part)
+
+    return event
+
+
 class Event(object):
     status_map = {
             "TENTATIVE": kolabformat.StatusTentative,
@@ -40,6 +57,7 @@ class Event(object):
     def __init__(self, from_ical="", from_string=""):
         self._attendees = []
         self._categories = []
+        self._attachment_parts = []
 
         if from_ical == "":
             if from_string == "":
@@ -751,15 +769,50 @@ class Event(object):
 
         msg["Subject"] = self.get_uid()
 
-        part.set_payload(str(self))
+        # extract attachment data into separate MIME parts
+        vattach = self.event.attachments()
+        i = 0
+        for attach in vattach:
+            if attach.uri():
+                continue
+
+            mimetype = attach.mimetype()
+            (primary, seconday) = mimetype.split('/')
+            name = attach.label()
+            if not name:
+                name = 'unknown.x'
 
-        # TODO: extract attachment data to separate MIME parts
+            (basename, suffix) = path.splitext(name)
+            t = datetime.datetime.now()
+            cid = "%s.%s.%s%s" % (basename, time.mktime(t.timetuple()), t.microsecond + len(self._attachment_parts), suffix)
+
+            p = MIMEBase(primary, seconday)
+            p.add_header('Content-Disposition', 'attachment', filename=name)
+            p.add_header('Content-Transfer-Encoding', 'base64')
+            p.add_header('Content-ID', '<' + cid + '>')
+            p.set_payload(base64.b64encode(attach.data()))
+
+            self._attachment_parts.append(p)
+
+            # modify attachment object
+            attach.setData('', mimetype)
+            attach.setUri('cid:' + cid, mimetype)
+            vattach[i] = attach
+            i += 1
+
+        self.event.setAttachments(vattach)
+
+        part.set_payload(str(self))
 
         part.add_header('Content-Disposition', 'attachment; filename="kolab.xml"')
         part.replace_header('Content-Transfer-Encoding', '8bit')
 
         msg.attach(part)
 
+        # append attachment parts
+        for p in self._attachment_parts:
+            msg.attach(p)
+
         return msg
 
     def to_message_itip(self, from_address, method="REQUEST", participant_status="ACCEPTED", subject=None, message_text=None):
diff --git a/tests/unit/test-003-event.py b/tests/unit/test-003-event.py
index f9ef92e..363c75e 100644
--- a/tests/unit/test-003-event.py
+++ b/tests/unit/test-003-event.py
@@ -12,6 +12,58 @@ from pykolab.xml import InvalidAttendeeParticipantStatusError
 from pykolab.xml import InvalidEventDateError
 from pykolab.xml import event_from_ical
 from pykolab.xml import event_from_string
+from pykolab.xml import event_from_message
+
+ical_event = """
+BEGIN:VEVENT
+UID:7a35527d-f783-4b58-b404-b1389bd2fc57
+DTSTAMP;VALUE=DATE-TIME:20140407T122311Z
+CREATED;VALUE=DATE-TIME:20140407T122245Z
+LAST-MODIFIED;VALUE=DATE-TIME:20140407T122311Z
+DTSTART;TZID=Europe/Zurich;VALUE=DATE-TIME:20140523T110000
+DURATION:PT1H30M0S
+RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10
+EXDATE;TZID=Europe/Zurich;VALUE=DATE-TIME:20140530T110000
+EXDATE;TZID=Europe/Zurich;VALUE=DATE-TIME:20140620T110000
+SUMMARY:Summary
+LOCATION:Location
+DESCRIPTION:Description\\n2 lines
+CATEGORIES:Personal
+TRANSP:OPAQUE
+PRIORITY:2
+SEQUENCE:2
+CLASS:PUBLIC
+ATTENDEE;CN="Manager, Jane";PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;CUTYP
+ E=INDIVIDUAL;RSVP=TRUE:mailto:jane.manager at example.org
+ATTENDEE;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION;ROLE=OPT-PARTICIPANT;RSVP=FA
+ LSE:MAILTO:max at imum.com
+ORGANIZER;CN=Doe\, John:mailto:john.doe at example.org
+URL:http://somelink.com/foo
+ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=image/png;X-LABEL=silhouette.pn
+ g:iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAIAAADY27xgAAAAGXRFWHRTb2Z0d2FyZQBBZG9i
+ ZSBJbWFnZVJlYWR5ccllPAAAAsRJREFUeNrsmeluKjEMhTswrAWB4P3fECGx79CjsTDmOKRkpF
+ xxpfoHSmchX7ybFrfb7eszpPH1MfKH8ofyH6KUtd/c7/en0wmfWBdF0Wq1Op1Ou91uNGoer6iX
+ V1ar1Xa7xUJeB4qsr9frdyVlWWZH2VZyPp+xPXHIAoK70+m02+1m9JXj8bhcLi+Xi3J4xUCazS
+ bUltdtd7ud7ldUIhC3u+iTwF0sFhlR4Kds4LtRZK1w4te5UM6V6JaqhqC3CQ28OAsKggJfbZ3U
+ eozCqZ4koHIZCGmD9ivuos9YONFirmxrI0UNZG1kbZeUXdJQNJNa91RlqMn0ekYUMZDup6dXVV
+ m+1OSZhqLx6bVCELJGSsyFQtFrF15JGYMZgoxubWGDSDVhvTipDKWhoBOIpFobxtlbJ0Gh0/tg
+ lgXal4woUHi/36fQoBQncDAlupa8DeVwOPRe4lUyGAwQ+dl7W+xBXkJBhEUqR32UoJfYIKrR4d
+ ZBgcdIRqfEqn+mekl9FNRbSTA249la3ev1/kXHD47ZbEYR5L9kMplkd9vNZqMFyIYxxfN8Pk8q
+ QGlagT5QDtfrNYUMlWW9LiGNPPSmC/+OgpK2r4RO6dOatZd+4gAAemdIi6Fg9EKLD4vASWkzv3
+ ew06NSCiA40CumAIoaIrhrcAwjF7aDo58gUchgNV+0n1BAcDgcoAZrXV9mI4qkhtK6FJFhi9Fo
+ ZKPsgQI1ACJieH/Kd570t+xFoIzHYzl5Q40CFGrSqGuks3qmYIKJfIl0nPKLxAMFw7Dv1+2QYf
+ vFSOBQubbOFDSc7ZcfWvHv6DzhOzT6IeOVPuz8Roex0f6EgsE/2IL4qdg7hIXz7/pBie7q1uWr
+ tp66xrif0l1KwUE4P7Y9Gci/ZgtNRFX+Rw06Q2RigsjuDc3urwKHxuNITaaxyD9mT2WvSDAXn/
+ Pvhh8BBgBjyfPSGbSYcwAAAABJRU5ErkJggg==
+ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/plain;X-LABEL=text.txt:VGh
+ pcyBpcyBhIHRleHQgZmlsZQo=
+BEGIN:VALARM
+ACTION:DISPLAY
+TRIGGER:-PT30M
+END:VALARM
+END:VEVENT
+"""
+
 
 class TestEventXML(unittest.TestCase):
     event = Event()
@@ -122,55 +174,8 @@ PRODID:-//Roundcube//Roundcube libcalendaring 1.1-git//Sabre//Sabre VObject
   2.1.3//EN
 CALSCALE:GREGORIAN
 METHOD:REQUEST
-BEGIN:VEVENT
-UID:7a35527d-f783-4b58-b404-b1389bd2fc57
-DTSTAMP;VALUE=DATE-TIME:20140407T122311Z
-CREATED;VALUE=DATE-TIME:20140407T122245Z
-LAST-MODIFIED;VALUE=DATE-TIME:20140407T122311Z
-DTSTART;TZID=Europe/Zurich;VALUE=DATE-TIME:20140523T110000
-DURATION:PT1H30M0S
-RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10
-EXDATE;TZID=Europe/Zurich;VALUE=DATE-TIME:20140530T110000
-EXDATE;TZID=Europe/Zurich;VALUE=DATE-TIME:20140620T110000
-SUMMARY:Summary
-LOCATION:Location
-DESCRIPTION:Description\\n2 lines
-CATEGORIES:Personal
-TRANSP:OPAQUE
-PRIORITY:2
-SEQUENCE:2
-CLASS:PUBLIC
-ATTENDEE;CN="Manager, Jane";PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;CUTYP
- E=INDIVIDUAL;RSVP=TRUE:mailto:jane.manager at example.org
-ATTENDEE;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION;ROLE=OPT-PARTICIPANT;RSVP=FA
- LSE:MAILTO:max at imum.com
-ORGANIZER;CN=Doe\, John:mailto:john.doe at example.org
-URL:http://somelink.com/foo
-ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=image/png;X-LABEL=silhouette.pn
- g:iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAIAAADY27xgAAAAGXRFWHRTb2Z0d2FyZQBBZG9i
- ZSBJbWFnZVJlYWR5ccllPAAAAsRJREFUeNrsmeluKjEMhTswrAWB4P3fECGx79CjsTDmOKRkpF
- xxpfoHSmchX7ybFrfb7eszpPH1MfKH8ofyH6KUtd/c7/en0wmfWBdF0Wq1Op1Ou91uNGoer6iX
- V1ar1Xa7xUJeB4qsr9frdyVlWWZH2VZyPp+xPXHIAoK70+m02+1m9JXj8bhcLi+Xi3J4xUCazS
- bUltdtd7ud7ldUIhC3u+iTwF0sFhlR4Kds4LtRZK1w4te5UM6V6JaqhqC3CQ28OAsKggJfbZ3U
- eozCqZ4koHIZCGmD9ivuos9YONFirmxrI0UNZG1kbZeUXdJQNJNa91RlqMn0ekYUMZDup6dXVV
- m+1OSZhqLx6bVCELJGSsyFQtFrF15JGYMZgoxubWGDSDVhvTipDKWhoBOIpFobxtlbJ0Gh0/tg
- lgXal4woUHi/36fQoBQncDAlupa8DeVwOPRe4lUyGAwQ+dl7W+xBXkJBhEUqR32UoJfYIKrR4d
- ZBgcdIRqfEqn+mekl9FNRbSTA249la3ev1/kXHD47ZbEYR5L9kMplkd9vNZqMFyIYxxfN8Pk8q
- QGlagT5QDtfrNYUMlWW9LiGNPPSmC/+OgpK2r4RO6dOatZd+4gAAemdIi6Fg9EKLD4vASWkzv3
- ew06NSCiA40CumAIoaIrhrcAwjF7aDo58gUchgNV+0n1BAcDgcoAZrXV9mI4qkhtK6FJFhi9Fo
- ZKPsgQI1ACJieH/Kd570t+xFoIzHYzl5Q40CFGrSqGuks3qmYIKJfIl0nPKLxAMFw7Dv1+2QYf
- vFSOBQubbOFDSc7ZcfWvHv6DzhOzT6IeOVPuz8Roex0f6EgsE/2IL4qdg7hIXz7/pBie7q1uWr
- tp66xrif0l1KwUE4P7Y9Gci/ZgtNRFX+Rw06Q2RigsjuDc3urwKHxuNITaaxyD9mT2WvSDAXn/
- Pvhh8BBgBjyfPSGbSYcwAAAABJRU5ErkJggg==
-ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/plain;X-LABEL=text.txt:VGh
- pcyBpcyBhIHRleHQgZmlsZQo=
-BEGIN:VALARM
-ACTION:DISPLAY
-TRIGGER:-PT30M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-"""
+        """ + ical_event + "END:VCALENDAR"
+
         ical = icalendar.Calendar.from_ical(ical_str)
         event = event_from_ical(ical.walk('VEVENT')[0].to_ical())
 
@@ -193,6 +198,26 @@ END:VCALENDAR
         self.assertEqual(len(event.get_alarms()), 1)
         self.assertEqual(len(event.get_attachments()), 2)
 
+    def test_018_ical_to_message(self):
+        event = event_from_ical(ical_event)
+        message = event.to_message()
+
+        self.assertTrue(message.is_multipart())
+        self.assertEqual(message['Subject'], event.uid)
+        self.assertEqual(message['X-Kolab-Type'], 'application/x-vnd.kolab.event')
+
+        parts = [p for p in message.walk()]
+        attachments = event.get_attachments();
+
+        self.assertEqual(len(parts), 5)
+        self.assertEqual(parts[1].get_content_type(), 'text/plain')
+        self.assertEqual(parts[2].get_content_type(), 'application/calendar+xml')
+        self.assertEqual(parts[3].get_content_type(), 'image/png')
+        self.assertEqual(parts[4].get_content_type(), 'text/plain')
+        self.assertEqual(parts[2]['Content-ID'], None)
+        self.assertEqual(parts[3]['Content-ID'].strip('<>'), attachments[0].uri()[4:])
+        self.assertEqual(parts[4]['Content-ID'].strip('<>'), attachments[1].uri()[4:])
+
     def test_019_as_string_itip(self):
         self.event.set_summary("test")
         self.event.set_start(datetime.datetime(2014, 05, 23, 11, 00, 00, tzinfo=pytz.timezone("Europe/London")))
@@ -348,6 +373,44 @@ END:VCALENDAR
         self.assertIsInstance(event.get_start(), datetime.datetime)
         self.assertEqual(str(event.get_start()), "2014-08-13 10:00:00+00:00")
 
+    def test_023_load_from_message(self):
+        event = event_from_message(event_from_ical(ical_event).to_message())
+        event.set_sequence(3)
+
+        message = event.to_message()
+        self.assertTrue(message.is_multipart())
+
+        # check attachment MIME parts are kept
+        parts = [p for p in message.walk()]
+        attachments = event.get_attachments();
+
+        self.assertEqual(len(parts), 5)
+        self.assertEqual(parts[3].get_content_type(), 'image/png')
+        self.assertEqual(parts[3]['Content-ID'].strip('<>'), attachments[0].uri()[4:])
+        self.assertEqual(parts[4].get_content_type(), 'text/plain')
+        self.assertEqual(parts[4]['Content-ID'].strip('<>'), attachments[1].uri()[4:])
+
+    def test_024_bogus_itip_data(self):
+        # DTSTAMP contains an invalid date/time value
+        vevent = """BEGIN:VEVENT
+UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271
+DTSTAMP:20120713T1254140
+DTSTART;TZID=Europe/London:20120713T100000
+DTEND;TZID=Europe/London:20120713T110000
+SUMMARY:test
+DESCRIPTION:test
+ORGANIZER;CN="Doe, John":mailto:john.doe at example.org
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailt
+ o:jane.doe at example.org
+ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailt
+ o:user.external at example.com
+SEQUENCE:1
+TRANSP:OPAQUE
+END:VEVENT
+"""
+        event = event_from_ical(vevent)
+        self.assertRaises(EventIntegrityError, event.to_message)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/unit/test-012-wallace_invitationpolicy.py b/tests/unit/test-012-wallace_invitationpolicy.py
index 75939d0..650879b 100644
--- a/tests/unit/test-012-wallace_invitationpolicy.py
+++ b/tests/unit/test-012-wallace_invitationpolicy.py
@@ -44,7 +44,7 @@ CALSCALE:GREGORIAN
 METHOD:REQUEST
 BEGIN:VEVENT
 UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271
-DTSTAMP:20120713T1254140
+DTSTAMP:20120713T125414Z
 DTSTART;TZID=3DEurope/London:20120713T100000
 DTEND;TZID=3DEurope/London:20120713T110000
 SUMMARY:test




More information about the commits mailing list