diff options
author | Paul Boddie <paul@boddie.org.uk> | 2014-08-06 17:48:26 +0200 |
---|---|---|
committer | Paul Boddie <paul@boddie.org.uk> | 2014-08-06 17:48:26 +0200 |
commit | fee17e6f7a5fa995f7d77c7822e3ed1f2f8bffb5 (patch) | |
tree | b401d3b6c748c09895ec4f0a887f3f2f0cdb6e0d /tests/functional/test_wallace/test_007_invitationpolicy.py | |
parent | e0e89b980b8671eabf682cd83efc603775048228 (diff) | |
parent | a8555e3e8789fd02b7d5749ea4fc51b84e57285f (diff) | |
download | pykolab-fee17e6f7a5fa995f7d77c7822e3ed1f2f8bffb5.tar.gz |
Merge branch 'master' of git://git.kolab.org/git/pykolab into dev/boddiedev/boddie
Conflicts:
conf/kolab.conf
po/POTFILES.in
pykolab/auth/ldap/__init__.py
pykolab/auth/ldap/cache.py
pykolab/cli/cmd_rename_mailbox.py
pykolab/imap/__init__.py
pykolab/imap/cyrus.py
pykolab/wap_client/__init__.py
pykolab/xml/event.py
Diffstat (limited to 'tests/functional/test_wallace/test_007_invitationpolicy.py')
-rw-r--r-- | tests/functional/test_wallace/test_007_invitationpolicy.py | 737 |
1 files changed, 737 insertions, 0 deletions
diff --git a/tests/functional/test_wallace/test_007_invitationpolicy.py b/tests/functional/test_wallace/test_007_invitationpolicy.py new file mode 100644 index 0000000..9bc808f --- /dev/null +++ b/tests/functional/test_wallace/test_007_invitationpolicy.py @@ -0,0 +1,737 @@ +import time +import pykolab +import smtplib +import email +import datetime +import pytz +import uuid +import kolabformat + +from pykolab.imap import IMAP +from wallace import module_resources + +from pykolab.translate import _ +from pykolab.xml import event_from_message +from pykolab.xml import participant_status_label +from email import message_from_string +from twisted.trial import unittest + +import tests.functional.resource_func as funcs + +conf = pykolab.getConf() + +itip_invitation = """ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:%(uid)s +DTSTAMP:20140213T125414Z +DTSTART;TZID=Europe/Berlin:%(start)s +DTEND;TZID=Europe/Berlin:%(end)s +SUMMARY:%(summary)s +DESCRIPTION:test +ORGANIZER;CN="Doe, John":mailto:john.doe@example.org +ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=%(partstat)s;RSVP=TRUE:mailto:%(mailto)s +ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=TENTATIVE;RSVP=FALSE:mailto:somebody@else.com +TRANSP:OPAQUE +SEQUENCE:%(sequence)d +END:VEVENT +END:VCALENDAR +""" + +itip_cancellation = """ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:%(uid)s +DTSTAMP:20140218T125414Z +DTSTART;TZID=Europe/Berlin:20120713T100000 +DTEND;TZID=Europe/Berlin:20120713T110000 +SUMMARY:%(summary)s +DESCRIPTION:test +ORGANIZER;CN="Doe, John":mailto:john.doe@example.org +ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE:mailto:%(mailto)s +TRANSP:OPAQUE +SEQUENCE:%(sequence)d +END:VEVENT +END:VCALENDAR +""" + +itip_recurring = """ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//Mac OS X 10.9.2//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:%(uid)s +DTSTAMP:20140213T125414Z +DTSTART;TZID=Europe/Zurich:%(start)s +DTEND;TZID=Europe/Zurich:%(end)s +RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10 +SUMMARY:%(summary)s +DESCRIPTION:test +ORGANIZER;CN="Doe, John":mailto:john.doe@example.org +ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=%(partstat)s;RSVP=TRUE:mailto:%(mailto)s +TRANSP:OPAQUE +SEQUENCE:%(sequence)d +END:VEVENT +END:VCALENDAR +""" + +itip_reply = """ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//pykolab-0.6.9-1//kolab.org// +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +SUMMARY:%(summary)s +UID:%(uid)s +DTSTART;TZID=Europe/Berlin;VALUE=DATE-TIME:%(start)s +DTEND;TZID=Europe/Berlin;VALUE=DATE-TIME:%(end)s +DTSTAMP;VALUE=DATE-TIME:20140706T171038Z +ORGANIZER;CN="Doe, John":MAILTO:%(organizer)s +ATTENDEE;CUTYPE=INDIVIDUAL;PARTSTAT=%(partstat)s;ROLE=REQ-PARTICIPANT:mailto:%(mailto)s +PRIORITY:0 +SEQUENCE:%(sequence)d +END:VEVENT +END:VCALENDAR +""" + +mime_message = """MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="=_c8894dbdb8baeedacae836230e3436fd" +From: "Doe, John" <john.doe@example.org> +Date: Tue, 25 Feb 2014 13:54:14 +0100 +Message-ID: <240fe7ae7e139129e9eb95213c1016d7@example.org> +To: %s +Subject: "test" + +--=_c8894dbdb8baeedacae836230e3436fd +Content-Type: text/plain; charset=UTF-8; format=flowed +Content-Transfer-Encoding: quoted-printable + +*test* + +--=_c8894dbdb8baeedacae836230e3436fd +Content-Type: text/calendar; charset=UTF-8; method=%s; name=event.ics +Content-Disposition: attachment; filename=event.ics +Content-Transfer-Encoding: 8bit + +%s +--=_c8894dbdb8baeedacae836230e3436fd-- +""" + +class TestWallaceInvitationpolicy(unittest.TestCase): + + john = None + itip_reply_subject = None + + @classmethod + def setUp(self): + """ Compatibility for twisted.trial.unittest + """ + if not self.john: + self.setup_class() + + @classmethod + def setup_class(self, *args, **kw): + self.itip_reply_subject = _('"%(summary)s" has been %(status)s') + + from tests.functional.purge_users import purge_users + purge_users() + + self.john = { + 'displayname': 'John Doe', + 'mail': 'john.doe@example.org', + 'dn': 'uid=doe,ou=People,dc=example,dc=org', + 'preferredlanguage': 'en_US', + 'mailbox': 'user/john.doe@example.org', + 'kolabtargetfolder': 'user/john.doe/Calendar@example.org', + 'kolabinvitationpolicy': ['ACT_UPDATE_AND_NOTIFY','ACT_MANUAL'] + } + + self.jane = { + 'displayname': 'Jane Manager', + 'mail': 'jane.manager@example.org', + 'dn': 'uid=manager,ou=People,dc=example,dc=org', + 'preferredlanguage': 'en_US', + 'mailbox': 'user/jane.manager@example.org', + 'kolabtargetfolder': 'user/jane.manager/Calendar@example.org', + 'kolabinvitationpolicy': ['ACT_ACCEPT_IF_NO_CONFLICT','ACT_REJECT_IF_CONFLICT','ACT_UPDATE'] + } + + self.jack = { + 'displayname': 'Jack Tentative', + 'mail': 'jack.tentative@example.org', + 'dn': 'uid=tentative,ou=People,dc=example,dc=org', + 'preferredlanguage': 'en_US', + 'mailbox': 'user/jack.tentative@example.org', + 'kolabtargetfolder': 'user/jack.tentative/Calendar@example.org', + 'kolabinvitationpolicy': ['ACT_TENTATIVE_IF_NO_CONFLICT','ACT_SAVE_TO_CALENDAR','ACT_UPDATE'] + } + + self.mark = { + 'displayname': 'Mark German', + 'mail': 'mark.german@example.org', + 'dn': 'uid=german,ou=People,dc=example,dc=org', + 'preferredlanguage': 'de_DE', + 'mailbox': 'user/mark.german@example.org', + 'kolabtargetfolder': 'user/mark.german/Calendar@example.org', + 'kolabinvitationpolicy': ['ACT_ACCEPT','ACT_UPDATE_AND_NOTIFY'] + } + + self.external = { + 'displayname': 'Bob External', + 'mail': 'bob.external@gmail.com' + } + + from tests.functional.user_add import user_add + user_add("John", "Doe", kolabinvitationpolicy=self.john['kolabinvitationpolicy'], preferredlanguage=self.john['preferredlanguage']) + user_add("Jane", "Manager", kolabinvitationpolicy=self.jane['kolabinvitationpolicy'], preferredlanguage=self.jane['preferredlanguage']) + user_add("Jack", "Tentative", kolabinvitationpolicy=self.jack['kolabinvitationpolicy'], preferredlanguage=self.jack['preferredlanguage']) + user_add("Mark", "German", kolabinvitationpolicy=self.mark['kolabinvitationpolicy'], preferredlanguage=self.mark['preferredlanguage']) + + time.sleep(1) + from tests.functional.synchronize import synchronize_once + synchronize_once() + + def send_message(self, itip_payload, to_addr, from_addr=None, method="REQUEST"): + if from_addr is None: + from_addr = self.john['mail'] + + smtp = smtplib.SMTP('localhost', 10026) + smtp.sendmail(from_addr, to_addr, mime_message % (to_addr, method, itip_payload)) + + def send_itip_invitation(self, attendee_email, start=None, allday=False, template=None, summary="test", sequence=0, partstat='NEEDS-ACTION'): + if start is None: + start = datetime.datetime.now() + + uid = str(uuid.uuid4()) + + if allday: + default_template = itip_allday + end = start + datetime.timedelta(days=1) + date_format = '%Y%m%d' + else: + end = start + datetime.timedelta(hours=4) + default_template = itip_invitation + date_format = '%Y%m%dT%H%M%S' + + self.send_message((template if template is not None else default_template) % { + 'uid': uid, + 'start': start.strftime(date_format), + 'end': end.strftime(date_format), + 'mailto': attendee_email, + 'summary': summary, + 'sequence': sequence, + 'partstat': partstat + }, + attendee_email) + + return uid + + def send_itip_update(self, attendee_email, uid, start=None, template=None, summary="test", sequence=1, partstat='ACCEPTED'): + if start is None: + start = datetime.datetime.now() + + end = start + datetime.timedelta(hours=4) + self.send_message((template if template is not None else itip_invitation) % { + 'uid': uid, + 'start': start.strftime('%Y%m%dT%H%M%S'), + 'end': end.strftime('%Y%m%dT%H%M%S'), + 'mailto': attendee_email, + 'summary': summary, + 'sequence': sequence, + 'partstat': partstat + }, + attendee_email) + + return uid + + def send_itip_reply(self, uid, attendee_email, mailto, start=None, template=None, summary="test", sequence=0, partstat='ACCEPTED'): + if start is None: + start = datetime.datetime.now() + + end = start + datetime.timedelta(hours=4) + self.send_message((template if template is not None else itip_reply) % { + 'uid': uid, + 'start': start.strftime('%Y%m%dT%H%M%S'), + 'end': end.strftime('%Y%m%dT%H%M%S'), + 'mailto': attendee_email, + 'organizer': mailto, + 'summary': summary, + 'sequence': sequence, + 'partstat': partstat + }, + mailto, + attendee_email, + method='REPLY') + + return uid + + def send_itip_cancel(self, attendee_email, uid, summary="test", sequence=1): + self.send_message(itip_cancellation % { + 'uid': uid, + 'mailto': attendee_email, + 'summary': summary, + 'sequence': sequence, + }, + attendee_email, + method='CANCEL') + + return uid + + def create_calendar_event(self, start=None, summary="test", sequence=0, user=None, attendees=None): + if start is None: + start = datetime.datetime.now(pytz.timezone("Europe/Berlin")) + if user is None: + user = self.john + if attendees is None: + attendees = [self.jane] + + end = start + datetime.timedelta(hours=4) + + event = pykolab.xml.Event() + event.set_start(start) + event.set_end(end) + event.set_organizer(user['mail'], user['displayname']) + + for attendee in attendees: + event.add_attendee(attendee['mail'], attendee['displayname'], role="REQ-PARTICIPANT", participant_status="NEEDS-ACTION", rsvp=True) + + 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() + + mailbox = imap.folder_quote(user['kolabtargetfolder']) + imap.set_acl(mailbox, "cyrus-admin", "lrswipkxtecda") + imap.imap.m.select(mailbox) + + result = imap.imap.m.append( + mailbox, + None, + None, + event.to_message().as_string() + ) + + return event.get_uid() + + def update_calendar_event(self, uid, start=None, summary=None, sequence=0, user=None): + if user is None: + user = self.john + + event = self.check_user_calendar_event(user['kolabtargetfolder'], uid) + if event: + if start is not None: + event.set_start(start) + if summary is not None: + event.set_summary(summary) + if sequence is not None: + event.set_sequence(sequence) + + imap = IMAP() + imap.connect() + + mailbox = imap.folder_quote(user['kolabtargetfolder']) + imap.set_acl(mailbox, "cyrus-admin", "lrswipkxtecda") + imap.imap.m.select(mailbox) + + return imap.imap.m.append( + mailbox, + None, + None, + event.to_message().as_string() + ) + + return False + + def check_message_received(self, subject, from_addr=None, mailbox=None): + if mailbox is None: + mailbox = self.john['mailbox'] + + imap = IMAP() + imap.connect() + + mailbox = imap.folder_quote(mailbox) + imap.set_acl(mailbox, "cyrus-admin", "lrs") + imap.imap.m.select(mailbox) + + found = None + retries = 15 + + while not found and retries > 0: + retries -= 1 + + 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]) + if message['Subject'] == subject: + found = message + break + + time.sleep(1) + + imap.disconnect() + + return found + + def check_user_calendar_event(self, mailbox, uid=None): + imap = IMAP() + imap.connect() + + mailbox = imap.folder_quote(mailbox) + imap.set_acl(mailbox, "cyrus-admin", "lrs") + imap.imap.m.select(mailbox) + + found = None + retries = 15 + + while not found and retries > 0: + retries -= 1 + + typ, data = imap.imap.m.search(None, '(UNDELETED HEADER SUBJECT "%s")' % (uid) if uid else '(UNDELETED HEADER X-Kolab-Type "application/x-vnd.kolab.event")') + for num in data[0].split(): + 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 + + found = event_from_message(event_message) + if found: + break + + time.sleep(1) + + return found + + def purge_mailbox(self, mailbox): + imap = IMAP() + imap.connect() + mailbox = imap.folder_quote(mailbox) + imap.set_acl(mailbox, "cyrus-admin", "lrwcdest") + imap.imap.m.select(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_invite_accept_udate(self): + start = datetime.datetime(2014,8,13, 10,0,0) + uid = self.send_itip_invitation(self.jane['mail'], start) + + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.jane['mail']) + self.assertIsInstance(response, email.message.Message) + + event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_summary(), "test") + + # send update with the same sequence: no re-scheduling + self.send_itip_update(self.jane['mail'], uid, start, summary="test updated", sequence=0, partstat='ACCEPTED') + + time.sleep(10) + event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_summary(), "test updated") + self.assertEqual(event.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted) + + + # @depends on test_001_invite_user + def test_002_invite_conflict_reject(self): + uid = self.send_itip_invitation(self.jane['mail'], datetime.datetime(2014,8,13, 11,0,0), summary="test2") + + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test2', 'status':participant_status_label('DECLINED') }, self.jane['mail']) + self.assertIsInstance(response, email.message.Message) + + event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_summary(), "test2") + + + def test_003_invite_accept_tentative(self): + self.purge_mailbox(self.john['mailbox']) + + uid = self.send_itip_invitation(self.jack['mail'], datetime.datetime(2014,7,24, 8,0,0)) + + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.jack['mail']) + self.assertIsInstance(response, email.message.Message) + + + def test_004_copy_to_calendar(self): + self.purge_mailbox(self.john['mailbox']) + + self.send_itip_invitation(self.jack['mail'], datetime.datetime(2014,7,29, 8,0,0)) + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.jack['mail']) + self.assertIsInstance(response, email.message.Message) + + # send conflicting request to jack + uid = self.send_itip_invitation(self.jack['mail'], datetime.datetime(2014,7,29, 10,0,0), summary="test2") + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test2', 'status':participant_status_label('DECLINED') }, self.jack['mail']) + self.assertEqual(response, None, "No reply expected") + + event = self.check_user_calendar_event(self.jack['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_summary(), "test2") + self.assertEqual(event.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartNeedsAction) + + + def test_005_invite_rescheduling_accept(self): + self.purge_mailbox(self.john['mailbox']) + + start = datetime.datetime(2014,8,14, 9,0,0, tzinfo=pytz.timezone("Europe/Berlin")) + uid = self.send_itip_invitation(self.jane['mail'], start) + + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.jane['mail']) + self.assertIsInstance(response, email.message.Message) + + event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_summary(), "test") + + self.purge_mailbox(self.john['mailbox']) + + # send update with new date and incremented sequence + new_start = datetime.datetime(2014,8,15, 15,0,0, tzinfo=pytz.timezone("Europe/Berlin")) + self.send_itip_update(self.jane['mail'], uid, new_start, summary="test", sequence=1) + + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.jane['mail']) + self.assertIsInstance(response, email.message.Message) + + event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_start(), new_start) + self.assertEqual(event.get_sequence(), 1) + + + def test_005_invite_rescheduling_reject(self): + self.purge_mailbox(self.john['mailbox']) + self.purge_mailbox(self.jack['kolabtargetfolder']) + + start = datetime.datetime(2014,8,9, 17,0,0, tzinfo=pytz.timezone("Europe/Berlin")) + uid = self.send_itip_invitation(self.jack['mail'], start) + + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.jack['mail']) + self.assertIsInstance(response, email.message.Message) + + # send update with new but conflicting date and incremented sequence + self.create_calendar_event(datetime.datetime(2014,8,10, 10,30,0, tzinfo=pytz.timezone("Europe/Berlin")), user=self.jack) + new_start = datetime.datetime(2014,8,10, 9,30,0, tzinfo=pytz.timezone("Europe/Berlin")) + self.send_itip_update(self.jack['mail'], uid, new_start, summary="test (updated)", sequence=1) + + response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.jack['mail']) + self.assertEqual(response, None) + + # verify re-scheduled copy in jack's calendar with NEEDS-ACTION + event = self.check_user_calendar_event(self.jack['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_start(), new_start) + self.assertEqual(event.get_sequence(), 1) + + attendee = event.get_attendee(self.jack['mail']) + self.assertTrue(attendee.get_rsvp()) + self.assertEqual(attendee.get_participant_status(), kolabformat.PartNeedsAction) + + + def test_006_invitation_reply(self): + self.purge_mailbox(self.john['mailbox']) + + start = datetime.datetime(2014,8,18, 14,30,0, tzinfo=pytz.timezone("Europe/Berlin")) + uid = self.create_calendar_event(start, user=self.john) + + event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + + # send a reply from jane to john + self.send_itip_reply(uid, self.jane['mail'], self.john['mail'], start=start) + + # check for the updated event in john's calendar + time.sleep(10) + event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + + attendee = event.get_attendee(self.jane['mail']) + 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']) + + uid = self.send_itip_invitation(self.jane['mail'], summary="cancelled") + + response = self.check_message_received(self.itip_reply_subject % { 'summary':'cancelled', 'status':participant_status_label('ACCEPTED') }, self.jane['mail']) + self.assertIsInstance(response, email.message.Message) + + self.send_itip_cancel(self.jane['mail'], uid, summary="cancelled") + + time.sleep(10) + event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_summary(), "cancelled") + self.assertEqual(event.get_status(True), 'CANCELLED') + self.assertTrue(event.get_transparency()) + + + def test_008_inivtation_reply_notify(self): + self.purge_mailbox(self.john['mailbox']) + + start = datetime.datetime(2014,8,12, 16,0,0, tzinfo=pytz.timezone("Europe/Berlin")) + uid = self.create_calendar_event(start, user=self.john, attendees=[self.jane, self.mark, self.jack]) + + # send a reply from jane to john + self.send_itip_reply(uid, self.jane['mail'], self.john['mail'], start=start) + + # check for notification message + # this notification should be suppressed until mark has replied, too + notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.john['mail']) + self.assertEqual(notification, None) + + # send a reply from mark to john + self.send_itip_reply(uid, self.mark['mail'], self.john['mail'], start=start, partstat='ACCEPTED') + + notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.john['mail']) + self.assertIsInstance(notification, email.message.Message) + + notification_text = str(notification.get_payload()); + self.assertIn(self.jane['mail'], notification_text) + self.assertIn(_("PENDING"), notification_text) + + self.purge_mailbox(self.john['mailbox']) + + # send a reply from mark to john + self.send_itip_reply(uid, self.jack['mail'], self.john['mail'], start=start, partstat='ACCEPTED') + + # this triggers an additional notification + notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.john['mail']) + self.assertIsInstance(notification, email.message.Message) + + notification_text = str(notification.get_payload()); + self.assertNotIn(_("PENDING"), notification_text) + + + def test_008_notify_translated(self): + self.purge_mailbox(self.mark['mailbox']) + + start = datetime.datetime(2014,8,12, 16,0,0, tzinfo=pytz.timezone("Europe/Berlin")) + uid = self.create_calendar_event(start, user=self.mark, attendees=[self.jane]) + + # send a reply from jane to mark + self.send_itip_reply(uid, self.jane['mail'], self.mark['mail'], start=start) + + # change translations to de_DE + pykolab.translate.setUserLanguage(self.mark['preferredlanguage']) + notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.mark['mail'], self.mark['mailbox']) + self.assertIsInstance(notification, email.message.Message) + + notification_text = str(notification.get_payload()); + self.assertIn(self.jane['mail'], notification_text) + self.assertIn(participant_status_label("ACCEPTED")+":", notification_text) + + # reset localization + pykolab.translate.setUserLanguage(conf.get('kolab','default_locale')) + + + def test_009_outdated_reply(self): + self.purge_mailbox(self.john['mailbox']) + + start = datetime.datetime(2014,9,2, 11,0,0, tzinfo=pytz.timezone("Europe/Berlin")) + uid = self.create_calendar_event(start, user=self.john, sequence=2) + + # send a reply from jane to john + self.send_itip_reply(uid, self.jane['mail'], self.john['mail'], start=start, sequence=1) + + # verify jane's attendee status was not updated + time.sleep(10) + event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + self.assertEqual(event.get_sequence(), 2) + self.assertEqual(event.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartNeedsAction) + + + def test_010_partstat_update_propagation(self): + # ATTENTION: this test requires wallace.invitationpolicy_autoupdate_other_attendees_on_reply to be enabled in config + + start = datetime.datetime(2014,8,21, 13,0,0, tzinfo=pytz.timezone("Europe/Berlin")) + uid = self.create_calendar_event(start, user=self.john, attendees=[self.jane, self.jack, self.external]) + + event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + + # send invitations to jack and jane + event_itip = event.as_string_itip() + self.send_itip_invitation(self.jane['mail'], start, template=event_itip) + self.send_itip_invitation(self.jack['mail'], start, template=event_itip) + + # wait for replies from jack and jane to be processed and propagated + time.sleep(10) + event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + + # check updated event in organizer's calendar + self.assertEqual(event.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted) + self.assertEqual(event.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartTentative) + + # check updated partstats in jane's calendar + janes = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid) + self.assertEqual(janes.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted) + self.assertEqual(janes.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartTentative) + + # check updated partstats in jack's calendar + jacks = self.check_user_calendar_event(self.jack['kolabtargetfolder'], uid) + self.assertEqual(jacks.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted) + self.assertEqual(jacks.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartTentative) + + # PART 2: create conflicting event in jack's calendar + new_start = datetime.datetime(2014,8,21, 6,0,0, tzinfo=pytz.timezone("Europe/Berlin")) + self.create_calendar_event(new_start, user=self.jack, attendees=[], summary="blocker") + + # re-schedule initial event to new date + self.update_calendar_event(uid, start=new_start, sequence=1, user=self.john) + self.send_itip_update(self.jane['mail'], uid, new_start, summary="test (updated)", sequence=1) + self.send_itip_update(self.jack['mail'], uid, new_start, summary="test (updated)", sequence=1) + + # wait for replies to be processed and propagated + time.sleep(10) + event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid) + self.assertIsInstance(event, pykolab.xml.Event) + + # check updated event in organizer's calendar (jack didn't reply yet) + self.assertEqual(event.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted) + self.assertEqual(event.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartTentative) + + # check partstats in jack's calendar: jack's status should remain needs-action + jacks = self.check_user_calendar_event(self.jack['kolabtargetfolder'], uid) + self.assertEqual(jacks.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted) + self.assertEqual(jacks.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartNeedsAction) + + |