summaryrefslogtreecommitdiffstats
path: root/tests/functional
diff options
context:
space:
mode:
Diffstat (limited to 'tests/functional')
-rw-r--r--tests/functional/resource_func.py4
-rw-r--r--tests/functional/test_wallace/test_005_resource_add.py17
-rw-r--r--tests/functional/test_wallace/test_005_resource_invitation.py265
-rw-r--r--tests/functional/test_wallace/test_007_invitationpolicy.py737
-rw-r--r--tests/functional/user_add.py4
5 files changed, 973 insertions, 54 deletions
diff --git a/tests/functional/resource_func.py b/tests/functional/resource_func.py
index 43aca96..ac80360 100644
--- a/tests/functional/resource_func.py
+++ b/tests/functional/resource_func.py
@@ -4,7 +4,7 @@ from pykolab import wap_client
conf = pykolab.getConf()
-def resource_add(type, cn, members=None, owner=None):
+def resource_add(type, cn, members=None, owner=None, **kw):
if type == None or type == '':
raise Exception
@@ -18,6 +18,8 @@ def resource_add(type, cn, members=None, owner=None):
'owner': owner
}
+ resource_details.update(kw)
+
result = wap_client.authenticate(conf.get('ldap', 'bind_dn'), conf.get('ldap', 'bind_pw'), conf.get('kolab', 'primary_domain'))
type_id = 0
diff --git a/tests/functional/test_wallace/test_005_resource_add.py b/tests/functional/test_wallace/test_005_resource_add.py
index 2de60fb..fc7f3ed 100644
--- a/tests/functional/test_wallace/test_005_resource_add.py
+++ b/tests/functional/test_wallace/test_005_resource_add.py
@@ -29,8 +29,8 @@ class TestResourceAdd(unittest.TestCase):
funcs.purge_resources()
self.audi = funcs.resource_add("car", "Audi A4")
self.passat = funcs.resource_add("car", "VW Passat")
- self.boxter = funcs.resource_add("car", "Porsche Boxter S")
- self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ])
+ self.boxter = funcs.resource_add("car", "Porsche Boxter S", kolabinvitationpolicy='ACT_ACCEPT_AND_NOTIFY')
+ self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ], kolabinvitationpolicy='ACT_ACCEPT')
from tests.functional.synchronize import synchronize_once
synchronize_once()
@@ -56,3 +56,16 @@ class TestResourceAdd(unittest.TestCase):
attrs = auth.get_entry_attributes(None, self.cars['dn'], ['*'])
self.assertIn('groupofuniquenames', attrs['objectclass'])
self.assertEqual(len(attrs['uniquemember']), 3)
+ self.assertEqual(attrs['kolabinvitationpolicy'], 'ACT_ACCEPT')
+
+ def test_003_get_resource_records(self):
+ resource_dns = module_resources.resource_record_from_email_address(self.cars['mail'])
+ self.assertEqual(resource_dns[0], self.cars['dn'])
+
+ resources = module_resources.get_resource_records(resource_dns)
+ self.assertEqual(len(resources), 4)
+
+ # check for (inherited) kolabinvitationpolicy values (bitmasks)
+ self.assertEqual(resources[self.cars['dn']]['kolabinvitationpolicy'], [module_resources.ACT_ACCEPT])
+ self.assertEqual(resources[self.audi['dn']]['kolabinvitationpolicy'], [module_resources.ACT_ACCEPT])
+ self.assertEqual(resources[self.boxter['dn']]['kolabinvitationpolicy'], [module_resources.ACT_ACCEPT_AND_NOTIFY])
diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index 946cb5f..61b9402 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -8,6 +8,10 @@ import uuid
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 pykolab.itip import events_from_message
from email import message_from_string
from twisted.trial import unittest
@@ -23,13 +27,14 @@ CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:%s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
DTSTART;TZID=Europe/London:%s
DTEND;TZID=Europe/London:%s
SUMMARY:test
DESCRIPTION:test
ORGANIZER;CN="Doe, John":mailto:john.doe@example.org
ATTENDEE;ROLE=REQ-PARTICIPANT;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:%s
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Somebody Else:mailto:somebody@else.com
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR
@@ -43,7 +48,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 +95,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 +117,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 +138,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
@@ -184,6 +189,8 @@ class TestResourceInvitation(unittest.TestCase):
@classmethod
def setup_class(self, *args, **kw):
+ self.itip_reply_subject = _("Reservation Request for %(summary)s was %(status)s")
+
from tests.functional.purge_users import purge_users
purge_users()
@@ -213,9 +220,12 @@ class TestResourceInvitation(unittest.TestCase):
self.boxter = funcs.resource_add("car", "Porsche Boxter S")
self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ])
- self.room1 = funcs.resource_add("confroom", "Room 101", owner=self.jane['dn'])
+ self.room1 = funcs.resource_add("confroom", "Room 101", owner=self.jane['dn'], kolabinvitationpolicy='ACT_ACCEPT_AND_NOTIFY')
self.room2 = funcs.resource_add("confroom", "Conference Room B-222")
- self.rooms = funcs.resource_add("collection", "Rooms", [ self.room1['dn'], self.room2['dn'] ], self.jane['dn'])
+ self.rooms = funcs.resource_add("collection", "Rooms", [ self.room1['dn'], self.room2['dn'] ], self.jane['dn'], kolabinvitationpolicy='ACT_ACCEPT_AND_NOTIFY')
+
+ self.room3 = funcs.resource_add("confroom", "CEOs Office 303")
+ self.viprooms = funcs.resource_add("collection", "VIP Rooms", [ self.room3['dn'] ], self.jane['dn'], kolabinvitationpolicy='ACT_MANUAL')
time.sleep(1)
from tests.functional.synchronize import synchronize_once
@@ -227,12 +237,14 @@ class TestResourceInvitation(unittest.TestCase):
smtp = smtplib.SMTP('localhost', 10026)
smtp.sendmail(from_addr, to_addr, mime_message % (to_addr, itip_payload))
+ smtp.quit()
- def send_itip_invitation(self, resource_email, start=None, allday=False, template=None):
+ def send_itip_invitation(self, resource_email, start=None, allday=False, template=None, uid=None):
if start is None:
start = datetime.datetime.now()
- uid = str(uuid.uuid4())
+ if uid is None:
+ uid = str(uuid.uuid4())
if allday:
default_template = itip_allday
@@ -328,17 +340,14 @@ 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
time.sleep(1)
+ imap.disconnect()
+
return found
def purge_mailbox(self, mailbox):
@@ -357,12 +366,10 @@ class TestResourceInvitation(unittest.TestCase):
def find_resource_by_email(self, email):
resource = None
- if (email.find(self.audi['mail']) >= 0):
- resource = self.audi
- if (email.find(self.passat['mail']) >= 0):
- resource = self.passat
- if (email.find(self.boxter['mail']) >= 0):
- resource = self.boxter
+ for r in [self.audi, self.passat, self.boxter, self.room1, self.room2]:
+ if (email.find(r['mail']) >= 0):
+ resource = r
+ break
return resource
@@ -379,7 +386,7 @@ class TestResourceInvitation(unittest.TestCase):
def test_002_invite_resource(self):
uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,7,13, 10,0,0))
- response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
@@ -387,10 +394,11 @@ class TestResourceInvitation(unittest.TestCase):
self.assertEqual(event.get_summary(), "test")
+ # @depends test_002_invite_resource
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("Reservation Request for test was DECLINED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
self.assertEqual(self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid), None)
@@ -402,7 +410,7 @@ class TestResourceInvitation(unittest.TestCase):
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("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
delegatee = self.find_resource_by_email(accept['from'])
@@ -412,7 +420,7 @@ class TestResourceInvitation(unittest.TestCase):
self.assertIsInstance(self.check_resource_calendar_event(delegatee['kolabtargetfolder'], uid), pykolab.xml.Event)
# resource collection responds with a DELEGATED message
- response = self.check_message_received("Reservation Request for test was DELEGATED", self.cars['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, self.cars['mail'])
self.assertIsInstance(response, email.message.Message)
self.assertIn("ROLE=NON-PARTICIPANT;RSVP=FALSE", str(response))
@@ -422,13 +430,13 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,4,1, 10,0,0))
- response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
self.purge_mailbox(self.john['mailbox'])
self.send_itip_update(self.audi['mail'], uid, datetime.datetime(2014,4,1, 12,0,0)) # conflict with myself
- response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
@@ -443,13 +451,13 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.cars['mail'], datetime.datetime(2014,4,24, 12,0,0))
# one of the collection members accepted the reservation
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
delegatee = self.find_resource_by_email(accept['from'])
# book that resource for the next day
self.send_itip_invitation(delegatee['mail'], datetime.datetime(2014,4,25, 14,0,0))
- accept2 = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept2 = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
# re-schedule first booking to a conflicting date
self.purge_mailbox(self.john['mailbox'])
@@ -457,7 +465,7 @@ class TestResourceInvitation(unittest.TestCase):
self.send_itip_update(delegatee['mail'], uid, datetime.datetime(2014,4,25, 12,0,0), template=update_template)
# expect response from another member of the initially delegated collection
- new_accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ new_accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(new_accept, email.message.Message)
new_delegatee = self.find_resource_by_email(new_accept['from'])
@@ -468,7 +476,7 @@ class TestResourceInvitation(unittest.TestCase):
self.assertIsInstance(event, pykolab.xml.Event)
# old resource responds with a DELEGATED message
- response = self.check_message_received("Reservation Request for test was DELEGATED", delegatee['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, delegatee['mail'])
self.assertIsInstance(response, email.message.Message)
# old reservation was removed from old delegate's calendar
@@ -489,7 +497,7 @@ class TestResourceInvitation(unittest.TestCase):
# make new reservation to the now free'd slot
self.send_itip_invitation(self.boxter['mail'], datetime.datetime(2014,5,1, 9,0,0))
- response = self.check_message_received("Reservation Request for test was ACCEPTED", self.boxter['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.boxter['mail'])
self.assertIsInstance(response, email.message.Message)
@@ -500,7 +508,7 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.cars['mail'], dt)
# wait for accept notification
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
delegatee = self.find_resource_by_email(accept['from'])
@@ -511,12 +519,12 @@ class TestResourceInvitation(unittest.TestCase):
self.send_itip_update(delegatee['mail'], uid, dt, template=update_template)
# get response from delegatee
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
self.assertIn(delegatee['mail'], accept['from'])
# no delegation response on updates
- self.assertEqual(self.check_message_received("Reservation Request for test was DELEGATED", self.cars['mail']), None)
+ self.assertEqual(self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, self.cars['mail']), None)
def test_008_allday_reservation(self):
@@ -524,7 +532,7 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,6,2), True)
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
@@ -532,7 +540,7 @@ class TestResourceInvitation(unittest.TestCase):
self.assertIsInstance(event.get_start(), datetime.date)
uid2 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,6,2, 16,0,0))
- response = self.check_message_received("Reservation Request for test was DECLINED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
@@ -543,19 +551,19 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,2,20, 12,0,0),
template=itip_recurring.replace(";COUNT=10", ""))
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
# check non-recurring against recurring
uid2 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,3,13, 10,0,0))
- response = self.check_message_received("Reservation Request for test was DECLINED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
self.purge_mailbox(self.john['mailbox'])
# check recurring against recurring
uid3 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,2,22, 8,0,0), template=itip_recurring)
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
@@ -570,7 +578,7 @@ class TestResourceInvitation(unittest.TestCase):
itip_invalid = itip_invitation.replace("DTSTART;", "X-DTSTART;")
self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,3,24, 19,30,0), template=itip_invalid)
- self.assertEqual(self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail']), None)
+ self.assertEqual(self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail']), None)
def test_011_owner_info(self):
@@ -578,7 +586,7 @@ class TestResourceInvitation(unittest.TestCase):
self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,6,19, 16,0,0))
- accept = self.check_message_received("Reservation Request for test was ACCEPTED", self.room1['mail'])
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room1['mail'])
self.assertIsInstance(accept, email.message.Message)
respose_text = str(accept.get_payload(0))
self.assertIn(self.jane['mail'], respose_text)
@@ -590,27 +598,184 @@ class TestResourceInvitation(unittest.TestCase):
self.send_itip_invitation(self.room2['mail'], datetime.datetime(2014,6,19, 16,0,0))
- accept = self.check_message_received("Reservation Request for test was ACCEPTED", self.room2['mail'])
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room2['mail'])
self.assertIsInstance(accept, email.message.Message)
respose_text = str(accept.get_payload(0))
self.assertIn(self.jane['mail'], respose_text)
self.assertIn(self.jane['displayname'], respose_text)
- def TODO_test_012_owner_notification(self):
+ def test_012_owner_notification(self):
self.purge_mailbox(self.john['mailbox'])
self.purge_mailbox(self.jane['mailbox'])
- self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,5,4, 13,0,0))
+ self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,8,4, 13,0,0))
# check notification message sent to resource owner (jane)
- notify = self.check_message_received("Reservation Request for test was ACCEPTED", self.room1['mail'], self.jane['mailbox'])
+ notify = self.check_message_received(_('Booking for %s has been %s') % (self.room1['cn'], participant_status_label('ACCEPTED')), self.room1['mail'], self.jane['mailbox'])
self.assertIsInstance(notify, email.message.Message)
- self.assertEqual(notify['From'], self.room1['mail'])
- self.assertEqual(notify['Cc'], self.jane['mail'])
+
+ notification_text = str(notify.get_payload())
+ self.assertIn(self.john['mail'], notification_text)
+ self.assertIn(participant_status_label('ACCEPTED'), notification_text)
+
+ self.purge_mailbox(self.john['mailbox'])
# check notification sent to collection owner (jane)
- self.send_itip_invitation(self.rooms['mail'], datetime.datetime(2014,5,4, 12,30,0))
+ self.send_itip_invitation(self.rooms['mail'], datetime.datetime(2014,8,4, 12,30,0))
+
+ # one of the collection members accepted the reservation
+ accepted = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
+ delegatee = self.find_resource_by_email(accepted['from'])
+
+ notify = self.check_message_received(_('Booking for %s has been %s') % (delegatee['cn'], participant_status_label('ACCEPTED')), delegatee['mail'], self.jane['mailbox'])
+ self.assertIsInstance(notify, email.message.Message)
+ self.assertIn(self.john['mail'], notification_text)
+
+
+ def test_013_owner_confirmation_accept(self):
+ self.purge_mailbox(self.john['mailbox'])
+ self.purge_mailbox(self.jane['mailbox'])
+
+ uid = self.send_itip_invitation(self.room3['mail'], datetime.datetime(2014,9,12, 14,0,0))
+
+ # requester (john) gets a TENTATIVE confirmation
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_summary(), "test")
+ self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'TENTATIVE')
+
+ # check confirmation message sent to resource owner (jane)
+ notify = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox'])
+ self.assertIsInstance(notify, email.message.Message)
+
+ itip_event = events_from_message(notify)[0]
+
+ # resource owner confirms reservation request
+ itip_reply = itip_event['xml'].to_message_itip(self.jane['mail'],
+ method="REPLY",
+ participant_status='ACCEPTED',
+ message_text="Request accepted",
+ subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('ACCEPTED'))
+ )
+
+ smtp = smtplib.SMTP('localhost', 10026)
+ smtp.sendmail(self.jane['mail'], str(itip_event['organizer']), str(itip_reply))
+ smtp.quit()
+
+ # requester (john) now gets the ACCEPTED response
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_status(True), 'CONFIRMED')
+ self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'ACCEPTED')
+
- notify = self.check_message_received("Reservation Request for test was ACCEPTED", self.room2['mail'], self.jane['mailbox'])
+ def test_014_owner_confirmation_decline(self):
+ self.purge_mailbox(self.john['mailbox'])
+ self.purge_mailbox(self.jane['mailbox'])
+
+ uid = self.send_itip_invitation(self.room3['mail'], datetime.datetime(2014,9,14, 9,0,0))
+
+ # requester (john) gets a TENTATIVE confirmation
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ # check confirmation message sent to resource owner (jane)
+ notify = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox'])
self.assertIsInstance(notify, email.message.Message)
+
+ itip_event = events_from_message(notify)[0]
+
+ # resource owner declines reservation request
+ itip_reply = itip_event['xml'].to_message_itip(self.jane['mail'],
+ method="REPLY",
+ participant_status='DECLINED',
+ message_text="Request declined",
+ subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('DECLINED'))
+ )
+
+ smtp = smtplib.SMTP('localhost', 10026)
+ smtp.sendmail(self.jane['mail'], str(itip_event['organizer']), str(itip_reply))
+ smtp.quit()
+
+ # requester (john) now gets the DECLINED response
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ # tentative reservation was set to cancelled
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertEqual(event, None)
+ #self.assertEqual(event.get_status(True), 'CANCELLED')
+ #self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'DECLINED')
+
+
+ def test_015_owner_confirmation_update(self):
+ self.purge_mailbox(self.john['mailbox'])
+
+ uid = self.send_itip_invitation(self.room3['mail'], datetime.datetime(2014,8,19, 9,0,0), uid="http://a-totally.stupid/?uid")
+
+ # requester (john) gets a TENTATIVE confirmation
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ # check first confirmation message sent to resource owner (jane)
+ notify1 = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox'])
+ self.assertIsInstance(notify1, email.message.Message)
+
+ itip_event1 = events_from_message(notify1)[0]
+ self.assertEqual(itip_event1['start'].hour, 9)
+
+ self.purge_mailbox(self.jane['mailbox'])
+ self.purge_mailbox(self.john['mailbox'])
+
+ # send update with new date (and sequence)
+ self.send_itip_update(self.room3['mail'], uid, datetime.datetime(2014,8,19, 16,0,0))
+
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'TENTATIVE')
+
+ # check second confirmation message sent to resource owner (jane)
+ notify2 = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox'])
+ self.assertIsInstance(notify2, email.message.Message)
+
+ itip_event2 = events_from_message(notify2)[0]
+ self.assertEqual(itip_event2['start'].hour, 16)
+
+ # resource owner declines the first reservation request
+ itip_reply = itip_event1['xml'].to_message_itip(self.jane['mail'],
+ method="REPLY",
+ participant_status='DECLINED',
+ message_text="Request declined",
+ subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('DECLINED'))
+ )
+ smtp = smtplib.SMTP('localhost', 10026)
+ smtp.sendmail(self.jane['mail'], str(itip_event1['organizer']), str(itip_reply))
+ smtp.quit()
+
+ time.sleep(5)
+
+ # resource owner accpets the second reservation request
+ itip_reply = itip_event2['xml'].to_message_itip(self.jane['mail'],
+ method="REPLY",
+ participant_status='ACCEPTED',
+ message_text="Request accepred",
+ subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('ACCEPTED'))
+ )
+ smtp = smtplib.SMTP('localhost', 10026)
+ smtp.sendmail(self.jane['mail'], str(itip_event2['organizer']), str(itip_reply))
+ smtp.quit()
+
+ # requester (john) now gets the ACCEPTED response
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'ACCEPTED')
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)
+
+
diff --git a/tests/functional/user_add.py b/tests/functional/user_add.py
index 4939f93..b1b37f1 100644
--- a/tests/functional/user_add.py
+++ b/tests/functional/user_add.py
@@ -4,7 +4,7 @@ from pykolab import wap_client
conf = pykolab.getConf()
-def user_add(givenname, sn, preferredlanguage='en_US'):
+def user_add(givenname, sn, preferredlanguage='en_US', **kw):
if givenname == None:
raise Exception
@@ -25,6 +25,8 @@ def user_add(givenname, sn, preferredlanguage='en_US'):
'userpassword': 'Welcome2KolabSystems'
}
+ user_details.update(kw)
+
login = conf.get('ldap', 'bind_dn')
password = conf.get('ldap', 'bind_pw')
domain = conf.get('kolab', 'primary_domain')