2009-08-27 18:20:41 +02:00
|
|
|
#!/usr/bin/python
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
# setup: 4 users are needed: username, attendee1_username,
|
|
|
|
# attendee1_delegate_username and superuser.
|
|
|
|
# when writing new tests, avoid using superuser when not absolutely needed
|
2009-08-27 18:20:41 +02:00
|
|
|
|
2012-06-27 18:06:20 +02:00
|
|
|
# TODO
|
|
|
|
# - Individual tests should set the ACLs themselves on Resources tests
|
|
|
|
|
2011-05-03 14:37:34 +02:00
|
|
|
from config import hostname, port, username, password, \
|
2012-05-11 18:20:45 +02:00
|
|
|
superuser, superuser_password, \
|
|
|
|
attendee1, attendee1_username, \
|
|
|
|
attendee1_password, \
|
|
|
|
attendee1_delegate, attendee1_delegate_username, \
|
|
|
|
attendee1_delegate_password, \
|
2011-05-03 14:37:34 +02:00
|
|
|
resource_no_overbook, resource_can_overbook
|
2009-08-27 18:20:41 +02:00
|
|
|
|
|
|
|
import datetime
|
2012-02-02 19:05:36 +01:00
|
|
|
import dateutil.tz
|
2010-07-13 18:02:56 +02:00
|
|
|
import sogotests
|
2009-08-27 18:20:41 +02:00
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import unittest
|
2010-07-13 18:02:56 +02:00
|
|
|
import utilities
|
2009-08-27 18:20:41 +02:00
|
|
|
import vobject
|
|
|
|
import vobject.base
|
|
|
|
import vobject.icalendar
|
|
|
|
import webdavlib
|
|
|
|
import StringIO
|
2010-06-01 20:14:17 +02:00
|
|
|
import xml.etree.ElementTree
|
2009-08-27 18:20:41 +02:00
|
|
|
|
2010-05-06 21:30:14 +02:00
|
|
|
class CalDAVPropertiesTest(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
|
|
self.client = webdavlib.WebDAVClient(hostname, port,
|
|
|
|
username, password)
|
|
|
|
self.test_calendar \
|
|
|
|
= "/SOGo/dav/%s/Calendar/test-dav-properties/" % username
|
|
|
|
mkcol = webdavlib.WebDAVMKCOL(self.test_calendar)
|
|
|
|
self.client.execute(mkcol)
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
delete = webdavlib.WebDAVDELETE(self.test_calendar)
|
|
|
|
self.client.execute(delete)
|
|
|
|
|
|
|
|
def testDavScheduleCalendarTransparency(self):
|
|
|
|
"""{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp"""
|
|
|
|
|
|
|
|
## PROPFIND
|
|
|
|
propfind = webdavlib.WebDAVPROPFIND(self.test_calendar,
|
|
|
|
["{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp"],
|
|
|
|
0)
|
|
|
|
self.client.execute(propfind)
|
2010-06-01 20:14:17 +02:00
|
|
|
response = propfind.response["document"].find('{DAV:}response')
|
|
|
|
propstat = response.find('{DAV:}propstat')
|
|
|
|
status = propstat.find('{DAV:}status').text[9:12]
|
|
|
|
|
2010-05-06 21:30:14 +02:00
|
|
|
self.assertEquals(status, "200",
|
|
|
|
"schedule-calendar-transp marked as 'Not Found' in response")
|
2010-06-01 20:14:17 +02:00
|
|
|
transp = propstat.find('{DAV:}prop/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp')
|
|
|
|
values = transp.getchildren()
|
|
|
|
self.assertEquals(len(values), 1, "one and only one element expected")
|
2010-05-06 21:30:14 +02:00
|
|
|
value = values[0]
|
2012-07-17 18:56:19 +02:00
|
|
|
self.assertTrue(xml.etree.ElementTree.iselement(value))
|
2010-06-01 20:14:17 +02:00
|
|
|
ns = value.tag[0:31]
|
|
|
|
tag = value.tag[31:]
|
|
|
|
self.assertTrue(ns == "{urn:ietf:params:xml:ns:caldav}",
|
|
|
|
"schedule-calendar-transp must have a value in"\
|
|
|
|
" namespace '%s', not '%s'"
|
|
|
|
% ("urn:ietf:params:xml:ns:caldav", ns))
|
|
|
|
self.assertTrue(tag == "opaque",
|
2010-05-06 21:30:14 +02:00
|
|
|
"schedule-calendar-transp must be 'opaque' on new" \
|
2010-06-01 20:14:17 +02:00
|
|
|
" collections, not '%s'" % tag)
|
2010-05-06 21:30:14 +02:00
|
|
|
|
|
|
|
## PROPPATCH
|
|
|
|
newValueNode = "{urn:ietf:params:xml:ns:caldav}thisvaluedoesnotexist"
|
|
|
|
proppatch = webdavlib.WebDAVPROPPATCH(self.test_calendar,
|
|
|
|
{"{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": \
|
|
|
|
{ newValueNode: True }})
|
|
|
|
self.client.execute(proppatch)
|
|
|
|
self.assertEquals(proppatch.response["status"], 400,
|
|
|
|
"expecting failure when setting transparency to" \
|
|
|
|
" an invalid value")
|
|
|
|
|
|
|
|
newValueNode = "{urn:ietf:params:xml:ns:caldav}transparent"
|
|
|
|
proppatch = webdavlib.WebDAVPROPPATCH(self.test_calendar,
|
|
|
|
{"{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": \
|
|
|
|
{ newValueNode: True }})
|
|
|
|
self.client.execute(proppatch)
|
|
|
|
self.assertEquals(proppatch.response["status"], 207,
|
|
|
|
"failure (%s) setting transparency to" \
|
|
|
|
" 'transparent': '%s'"
|
|
|
|
% (proppatch.response["status"],
|
|
|
|
proppatch.response["body"]))
|
|
|
|
|
|
|
|
newValueNode = "{urn:ietf:params:xml:ns:caldav}opaque"
|
|
|
|
proppatch = webdavlib.WebDAVPROPPATCH(self.test_calendar,
|
|
|
|
{"{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp": \
|
|
|
|
{ newValueNode: True }})
|
|
|
|
self.client.execute(proppatch)
|
|
|
|
self.assertEquals(proppatch.response["status"], 207,
|
|
|
|
"failure (%s) setting transparency to" \
|
|
|
|
" 'transparent': '%s'"
|
|
|
|
% (proppatch.response["status"],
|
|
|
|
proppatch.response["body"]))
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
class CalDAVSchedulingTest(unittest.TestCase):
|
2009-08-27 18:20:41 +02:00
|
|
|
def setUp(self):
|
2012-05-11 18:20:45 +02:00
|
|
|
self.superuser_client = webdavlib.WebDAVClient(hostname, port,
|
|
|
|
superuser, superuser_password)
|
2009-08-27 18:20:41 +02:00
|
|
|
self.client = webdavlib.WebDAVClient(hostname, port,
|
|
|
|
username, password)
|
2012-05-11 18:20:45 +02:00
|
|
|
self.attendee1_client = webdavlib.WebDAVClient(hostname, port,
|
|
|
|
attendee1_username, attendee1_password)
|
|
|
|
self.attendee1_delegate_client = webdavlib.WebDAVClient(hostname, port,
|
2012-07-13 22:25:47 +02:00
|
|
|
attendee1_delegate_username, attendee1_delegate_password)
|
2012-05-11 18:20:45 +02:00
|
|
|
|
2010-06-01 20:14:17 +02:00
|
|
|
utility = utilities.TestUtility(self, self.client)
|
|
|
|
(self.user_name, self.user_email) = utility.fetchUserInfo(username)
|
|
|
|
(self.attendee1_name, self.attendee1_email) = utility.fetchUserInfo(attendee1)
|
|
|
|
(self.attendee1_delegate_name, self.attendee1_delegate_email) = utility.fetchUserInfo(attendee1_delegate)
|
2011-05-03 14:37:34 +02:00
|
|
|
(self.res_no_ob_name, self.res_no_ob_email) = utility.fetchUserInfo(resource_no_overbook)
|
|
|
|
(self.res_can_ob_name, self.res_can_ob_email) = utility.fetchUserInfo(resource_can_overbook)
|
2009-08-27 18:20:41 +02:00
|
|
|
|
|
|
|
self.user_calendar = "/SOGo/dav/%s/Calendar/personal/" % username
|
|
|
|
self.attendee1_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1
|
|
|
|
self.attendee1_delegate_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1_delegate
|
2012-06-22 21:06:17 +02:00
|
|
|
self.res_calendar = "/SOGo/dav/%s/Calendar/personal/" % resource_no_overbook
|
|
|
|
self.res_ob_calendar = "/SOGo/dav/%s/Calendar/personal/" % resource_can_overbook
|
2009-08-27 18:20:41 +02:00
|
|
|
|
2012-05-11 19:45:44 +02:00
|
|
|
# fetch non existing event to let sogo create the calendars in the db
|
|
|
|
self._getEvent(self.client, "%snonexistent" % self.user_calendar, exp_status=404)
|
|
|
|
self._getEvent(self.attendee1_client, "%snonexistent" % self.attendee1_calendar, exp_status=404)
|
|
|
|
self._getEvent(self.attendee1_delegate_client, "%snonexistent" %
|
|
|
|
self.attendee1_delegate_calendar, exp_status=404)
|
|
|
|
|
2012-06-22 21:06:17 +02:00
|
|
|
# list of ics used by the test.
|
|
|
|
# tearDown will loop over this and wipe them in all users' calendar
|
|
|
|
self.ics_list = []
|
|
|
|
|
2011-05-03 14:37:34 +02:00
|
|
|
|
|
|
|
def tearDown(self):
|
2012-06-22 21:06:17 +02:00
|
|
|
# delete all created events from all users' calendar
|
|
|
|
for ics in self.ics_list:
|
|
|
|
self._deleteEvent(self.superuser_client,
|
|
|
|
"%s%s" % (self.user_calendar, ics), None)
|
|
|
|
self._deleteEvent(self.superuser_client,
|
|
|
|
"%s%s" % (self.attendee1_calendar, ics), None)
|
|
|
|
self._deleteEvent(self.superuser_client,
|
|
|
|
"%s%s" % (self.attendee1_delegate_calendar, ics), None)
|
|
|
|
self._deleteEvent(self.superuser_client,
|
|
|
|
"%s%s" % (self.res_calendar, ics), None)
|
|
|
|
self._deleteEvent(self.superuser_client,
|
|
|
|
"%s%s" % (self.res_ob_calendar, ics), None)
|
2011-05-03 14:37:34 +02:00
|
|
|
|
|
|
|
def _newEvent(self, summary="test event", uid="test", transp=0):
|
|
|
|
transparency = ("OPAQUE", "TRANSPARENT")
|
|
|
|
|
2009-08-27 18:20:41 +02:00
|
|
|
newCal = vobject.iCalendar()
|
|
|
|
vevent = newCal.add('vevent')
|
2011-05-03 14:37:34 +02:00
|
|
|
vevent.add('summary').value = summary
|
|
|
|
vevent.add('transp').value = transparency[transp]
|
2009-08-27 18:20:41 +02:00
|
|
|
|
2012-02-02 19:05:36 +01:00
|
|
|
now = datetime.datetime.now(dateutil.tz.gettz("America/Montreal"))
|
2009-08-27 18:20:41 +02:00
|
|
|
startdate = vevent.add('dtstart')
|
|
|
|
startdate.value = now
|
|
|
|
enddate = vevent.add('dtend')
|
|
|
|
enddate.value = now + datetime.timedelta(0, 3600)
|
2011-05-03 14:37:34 +02:00
|
|
|
vevent.add('uid').value = uid
|
2009-08-27 18:20:41 +02:00
|
|
|
vevent.add('dtstamp').value = now
|
|
|
|
vevent.add('last-modified').value = now
|
|
|
|
vevent.add('created').value = now
|
2012-05-11 18:20:45 +02:00
|
|
|
vevent.add('class').value = "PUBLIC"
|
2009-08-27 18:20:41 +02:00
|
|
|
vevent.add('sequence').value = "0"
|
|
|
|
|
|
|
|
return newCal
|
|
|
|
|
|
|
|
def _putEvent(self, client, filename, event, exp_status = 201):
|
|
|
|
put = webdavlib.HTTPPUT(filename, event.serialize())
|
|
|
|
put.content_type = "text/calendar; charset=utf-8"
|
|
|
|
client.execute(put)
|
|
|
|
if exp_status is not None:
|
|
|
|
self.assertEquals(put.response["status"], exp_status)
|
|
|
|
|
|
|
|
def _postEvent(self, client, outbox, event, originator, recipients,
|
|
|
|
exp_status = 200):
|
|
|
|
post = webdavlib.CalDAVPOST(outbox, event.serialize(),
|
|
|
|
originator, recipients)
|
|
|
|
client.execute(post)
|
|
|
|
if exp_status is not None:
|
|
|
|
self.assertEquals(post.response["status"], exp_status)
|
|
|
|
|
|
|
|
def _getEvent(self, client, filename, exp_status = 200):
|
|
|
|
get = webdavlib.HTTPGET(filename)
|
|
|
|
client.execute(get)
|
|
|
|
|
|
|
|
if exp_status is not None:
|
|
|
|
self.assertEquals(get.response["status"], exp_status)
|
|
|
|
|
|
|
|
if get.response["headers"]["content-type"].startswith("text/calendar"):
|
|
|
|
stream = StringIO.StringIO(get.response["body"])
|
|
|
|
event = vobject.base.readComponents(stream).next()
|
|
|
|
else:
|
|
|
|
event = None
|
|
|
|
|
|
|
|
return event
|
|
|
|
|
|
|
|
def _deleteEvent(self, client, filename, exp_status = 204):
|
|
|
|
delete = webdavlib.WebDAVDELETE(filename)
|
|
|
|
client.execute(delete)
|
|
|
|
if exp_status is not None:
|
|
|
|
self.assertEquals(delete.response["status"], exp_status)
|
|
|
|
|
2012-06-22 21:06:17 +02:00
|
|
|
def _getAllEvents(self, client, collection, exp_status = 207):
|
|
|
|
propfind = webdavlib.WebDAVPROPFIND(collection, None)
|
|
|
|
client.execute(propfind)
|
|
|
|
if exp_status is not None:
|
|
|
|
self.assertEquals(propfind.response["status"], exp_status)
|
|
|
|
|
|
|
|
content = []
|
|
|
|
nodes = propfind.response["document"].findall('{DAV:}response')
|
|
|
|
for node in nodes:
|
|
|
|
responseHref = node.find('{DAV:}href').text
|
|
|
|
content += [responseHref]
|
|
|
|
|
|
|
|
return content
|
|
|
|
|
|
|
|
def _deleteAllEvents(self, client, collection, exp_status = 204):
|
|
|
|
content = self._getAllEvents(client, collection)
|
|
|
|
for item in content:
|
|
|
|
self._deleteEvent(client, item)
|
|
|
|
|
2009-08-27 18:20:41 +02:00
|
|
|
def _eventAttendees(self, event):
|
|
|
|
attendees = {}
|
|
|
|
|
|
|
|
event_component = event.vevent
|
|
|
|
for child in event_component.getChildren():
|
|
|
|
if child.name == "ATTENDEE":
|
|
|
|
try:
|
|
|
|
delegated_to = child.delegated_to_param
|
|
|
|
except:
|
|
|
|
delegated_to = "(none)"
|
|
|
|
try:
|
|
|
|
delegated_from = child.delegated_from_param
|
|
|
|
except:
|
|
|
|
delegated_from = "(none)"
|
|
|
|
attendees[child.value] = ("%s/%s/%s"
|
|
|
|
% (child.partstat_param,
|
|
|
|
delegated_to,
|
|
|
|
delegated_from))
|
|
|
|
|
|
|
|
return attendees
|
|
|
|
|
|
|
|
def _compareAttendees(self, compared_event, event):
|
|
|
|
compared_attendees = self._eventAttendees(compared_event)
|
|
|
|
compared_emails = compared_attendees.keys()
|
|
|
|
self.assertTrue(len(compared_emails) > 0,
|
|
|
|
"no attendee found")
|
|
|
|
compared_emails.sort()
|
|
|
|
|
|
|
|
attendees = self._eventAttendees(event)
|
|
|
|
emails = attendees.keys()
|
|
|
|
emails.sort()
|
|
|
|
|
|
|
|
self.assertEquals(len(compared_emails), len(emails),
|
|
|
|
"number of attendees is not equal"
|
|
|
|
+ " (actual: %d, exp: %d)"
|
|
|
|
% (len(compared_emails), len(emails)))
|
|
|
|
|
|
|
|
for email in emails:
|
|
|
|
self.assertEquals(compared_attendees[email],
|
|
|
|
attendees[email],
|
|
|
|
"partstat for attendee '%s' does not match"
|
|
|
|
" (actual: '%s', expected: '%s')"
|
|
|
|
% (email,
|
|
|
|
compared_attendees[email], attendees[email]))
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
def testAddAttendee(self):
|
|
|
|
""" add attendee after event creation """
|
2012-04-20 23:33:35 +02:00
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
# make sure the event doesn't exist
|
2012-06-20 22:34:28 +02:00
|
|
|
ics_name = "test-add-attendee.ics"
|
2012-06-22 21:06:17 +02:00
|
|
|
self.ics_list += [ics_name]
|
|
|
|
|
2012-04-20 23:33:35 +02:00
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar,ics_name), None)
|
2012-05-11 18:20:45 +02:00
|
|
|
self._deleteEvent(self.attendee1_client,
|
2012-04-20 23:33:35 +02:00
|
|
|
"%s%s" % (self.attendee1_calendar,ics_name), None)
|
|
|
|
|
|
|
|
# 1. create an event in the organiser's calendar
|
2012-05-11 18:20:45 +02:00
|
|
|
event = self._newEvent(summary="Test add attendee", uid="Test add attendee")
|
2012-04-20 23:33:35 +02:00
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.value = self.user_email
|
2012-05-11 18:20:45 +02:00
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
2012-04-20 23:33:35 +02:00
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
# 2. add an attendee
|
2012-04-20 23:33:35 +02:00
|
|
|
event.add("method").value = "REQUEST"
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.attendee1_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.attendee1_email
|
2012-05-11 18:20:45 +02:00
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event,
|
|
|
|
exp_status=204)
|
2012-04-20 23:33:35 +02:00
|
|
|
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
# 3. verify that the attendee has the event
|
|
|
|
attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
|
2012-04-20 23:33:35 +02:00
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
# 4. make sure the received event match the original one
|
|
|
|
# XXX is this enough?
|
|
|
|
self.assertEquals(event.vevent.uid, attendee_event.vevent.uid)
|
|
|
|
|
|
|
|
def testUninviteAttendee(self):
|
|
|
|
""" Remove attendee after event creation """
|
2012-04-20 23:33:35 +02:00
|
|
|
|
|
|
|
# make sure the event doesn't exist
|
2012-05-11 18:20:45 +02:00
|
|
|
ics_name = "test-remove-attendee.ics"
|
2012-06-22 21:06:17 +02:00
|
|
|
self.ics_list += [ics_name]
|
|
|
|
|
2012-04-20 23:33:35 +02:00
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar,ics_name), None)
|
2012-05-11 18:20:45 +02:00
|
|
|
self._deleteEvent(self.attendee1_client,
|
2012-04-20 23:33:35 +02:00
|
|
|
"%s%s" % (self.attendee1_calendar,ics_name), None)
|
|
|
|
|
|
|
|
# 1. create an event in the organiser's calendar
|
2012-05-11 18:20:45 +02:00
|
|
|
event = self._newEvent(summary="Test uninvite attendee", uid="Test uninvite attendee")
|
2012-04-20 23:33:35 +02:00
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.value = self.user_email
|
|
|
|
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
# keep a copy around for updates without other attributes
|
|
|
|
noAttendeeEvent = vobject.iCalendar()
|
|
|
|
noAttendeeEvent.copy(event)
|
2012-04-20 23:33:35 +02:00
|
|
|
|
|
|
|
# 2. add an attendee
|
|
|
|
event.add("method").value = "REQUEST"
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.attendee1_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.attendee1_email
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event,
|
|
|
|
exp_status=204)
|
|
|
|
|
|
|
|
# 3. verify that the attendee has the event
|
2012-05-11 18:20:45 +02:00
|
|
|
attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
|
2012-04-20 23:33:35 +02:00
|
|
|
|
|
|
|
# 4. make sure the received event match the original one
|
|
|
|
self.assertEquals(event.vevent.uid, attendee_event.vevent.uid)
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
# 5. uninvite the attendee - put the event back without the attendee
|
2012-04-20 23:33:35 +02:00
|
|
|
now = datetime.datetime.now(dateutil.tz.gettz("America/Montreal"))
|
2012-05-11 18:20:45 +02:00
|
|
|
noAttendeeEvent.vevent.last_modified.value = now
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), noAttendeeEvent,
|
2012-04-20 23:33:35 +02:00
|
|
|
exp_status=204)
|
|
|
|
|
|
|
|
# 6. verify that the attendee doesn't have the event anymore
|
2012-05-11 18:20:45 +02:00
|
|
|
attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name), 404)
|
2012-04-20 23:33:35 +02:00
|
|
|
|
2011-05-03 14:37:34 +02:00
|
|
|
def testResourceNoOverbook(self):
|
2012-06-22 21:06:17 +02:00
|
|
|
""" try to overbook a resource """
|
2011-05-03 14:37:34 +02:00
|
|
|
|
2012-06-22 21:06:17 +02:00
|
|
|
# make sure there are no events in the resource calendar
|
|
|
|
self._deleteAllEvents(self.superuser_client, self.res_calendar)
|
|
|
|
|
|
|
|
# make sure the event doesn't exist
|
|
|
|
ics_name = "test-no-overbook.ics"
|
|
|
|
self.ics_list += [ics_name]
|
2011-05-03 14:37:34 +02:00
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar,ics_name), None)
|
|
|
|
|
2012-06-22 21:06:17 +02:00
|
|
|
ob_ics_name = "test-no-overbook-overlap.ics"
|
|
|
|
self.ics_list += [ob_ics_name]
|
2011-05-03 14:37:34 +02:00
|
|
|
self._deleteEvent(self.client,
|
2012-06-22 21:06:17 +02:00
|
|
|
"%s%s" % (self.user_calendar,ob_ics_name), None)
|
2011-05-03 14:37:34 +02:00
|
|
|
|
|
|
|
# 1. create an event in the organiser's calendar
|
2012-06-22 21:06:17 +02:00
|
|
|
event = self._newEvent(summary="Test no overbook", uid="test no overbook")
|
2011-05-03 14:37:34 +02:00
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.value = self.user_email
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.res_no_ob_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.res_no_ob_email
|
2012-06-22 21:06:17 +02:00
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
2011-05-03 14:37:34 +02:00
|
|
|
|
|
|
|
# 2. create a second event overlapping the first one
|
2012-06-22 21:06:17 +02:00
|
|
|
event = self._newEvent(summary="Test no overbook - overlap", uid="test no overbook - overlap")
|
2011-05-03 14:37:34 +02:00
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.value = self.user_email
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.res_no_ob_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.res_no_ob_email
|
|
|
|
|
2012-06-22 21:06:17 +02:00
|
|
|
# put the event - should trigger a 403
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ob_ics_name), event, exp_status=403)
|
2011-05-03 14:37:34 +02:00
|
|
|
|
|
|
|
def testResourceCanOverbook(self):
|
2012-06-22 21:06:17 +02:00
|
|
|
""" try to overbook a resource - multiplebookings=0"""
|
2011-05-03 14:37:34 +02:00
|
|
|
|
2012-06-22 21:06:17 +02:00
|
|
|
# make sure there are no events in the resource calendar
|
|
|
|
self._deleteAllEvents(self.superuser_client, self.res_ob_calendar)
|
|
|
|
|
|
|
|
# make sure the event doesn't exist
|
|
|
|
ics_name = "test-can-overbook.ics"
|
|
|
|
self.ics_list += [ics_name]
|
2011-05-03 14:37:34 +02:00
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar,ics_name), None)
|
|
|
|
|
2012-06-22 21:06:17 +02:00
|
|
|
ob_ics_name = "test-can-overbook-overlap.ics"
|
|
|
|
self.ics_list += [ob_ics_name]
|
2011-05-03 14:37:34 +02:00
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar,ob_ics_name), None)
|
|
|
|
|
|
|
|
# 1. create an event in the organiser's calendar
|
2012-06-22 21:06:17 +02:00
|
|
|
event = self._newEvent(summary="Test can overbook", uid="test can overbook")
|
2011-05-03 14:37:34 +02:00
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.value = self.user_email
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.res_can_ob_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.res_can_ob_email
|
2012-06-22 21:06:17 +02:00
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
2011-05-03 14:37:34 +02:00
|
|
|
|
|
|
|
# 2. create a second event overlapping the first one
|
2012-06-22 21:06:17 +02:00
|
|
|
event = self._newEvent(summary="Test can overbook - overlap", uid="test can overbook - overlap")
|
2011-05-03 14:37:34 +02:00
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.value = self.user_email
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.res_can_ob_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.res_can_ob_email
|
|
|
|
|
2012-06-22 21:06:17 +02:00
|
|
|
# put the event - should be fine since we can overbook this one
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ob_ics_name), event)
|
|
|
|
|
|
|
|
def testResourceBookingOverlapDetection(self):
|
|
|
|
""" Resource booking overlap detection - bug #1837"""
|
|
|
|
|
|
|
|
# There used to be some problems with recurring events and resources booking
|
|
|
|
# This test implements these edge cases
|
|
|
|
|
|
|
|
# 1. Create recurring event (with resource)
|
|
|
|
# 2. Create single event overlaping one instance for the previous event
|
|
|
|
# (should fail)
|
|
|
|
# 3. Create recurring event which _doesn't_ overlap the first event
|
|
|
|
# (should be OK, used to fail pre1.3.17)
|
|
|
|
# 4. Create recurring event overlapping the previous recurring event
|
|
|
|
# (should fail)
|
|
|
|
|
|
|
|
# make sure there are no events in the resource calendar
|
|
|
|
self._deleteAllEvents(self.superuser_client, self.res_calendar)
|
|
|
|
|
|
|
|
# make sure the event doesn't exist
|
|
|
|
ics_name = "test-res-overlap-detection.ics"
|
|
|
|
self.ics_list += [ics_name]
|
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar,ics_name), None)
|
|
|
|
|
|
|
|
overlap_ics_name = "test-res-overlap-detection-overlap.ics"
|
|
|
|
self.ics_list += [overlap_ics_name]
|
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.attendee1_calendar,overlap_ics_name), None)
|
|
|
|
|
|
|
|
nooverlap_recurring_ics_name = "test-res-overlap-detection-nooverlap.ics"
|
|
|
|
self.ics_list += [nooverlap_recurring_ics_name]
|
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar,nooverlap_recurring_ics_name), None)
|
|
|
|
|
|
|
|
overlap_recurring_ics_name = "test-res-overlap-detection-overlap-recurring.ics"
|
|
|
|
self.ics_list += [overlap_recurring_ics_name]
|
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar,overlap_recurring_ics_name), None)
|
|
|
|
|
|
|
|
# 1. create recurring event with resource
|
|
|
|
event = self._newEvent(summary="recurring event with resource",
|
|
|
|
uid="recurring event w resource")
|
|
|
|
event.vevent.add('rrule').value = "FREQ=DAILY;COUNT=5"
|
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.value = self.user_email
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.res_no_ob_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.res_no_ob_email
|
|
|
|
|
|
|
|
# keep a copy around for #3
|
|
|
|
nooverlap_event = vobject.iCalendar()
|
|
|
|
nooverlap_event.copy(event)
|
|
|
|
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
|
|
|
|
|
|
|
# 2. Create single event overlaping one instance for the previous event
|
|
|
|
event = self._newEvent(summary="recurring event with resource",
|
|
|
|
uid="recurring event w resource - overlap")
|
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.attendee1_name
|
|
|
|
organizer.value = self.attendee1_email
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.res_no_ob_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.res_no_ob_email
|
|
|
|
# should fail
|
2012-09-21 19:16:50 +02:00
|
|
|
self._putEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, overlap_ics_name), event, exp_status=403)
|
2012-06-22 21:06:17 +02:00
|
|
|
|
|
|
|
# 3. Create recurring event which _doesn't_ overlap the first event
|
|
|
|
# (should be OK, used to fail pre1.3.17)
|
|
|
|
# shift the start date to one hour after the original event end time
|
|
|
|
nstartdate = nooverlap_event.vevent.dtend.value + datetime.timedelta(0, 3600)
|
|
|
|
nooverlap_event.vevent.dtstart.value = nstartdate
|
|
|
|
nooverlap_event.vevent.dtend.value = nstartdate + datetime.timedelta(0, 3600)
|
|
|
|
nooverlap_event.vevent.uid.value = "recurring - nooverlap"
|
|
|
|
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, nooverlap_recurring_ics_name), nooverlap_event)
|
|
|
|
|
|
|
|
# 4. Create recurring event overlapping the previous recurring event
|
|
|
|
# should fail
|
|
|
|
nstartdate = nooverlap_event.vevent.dtstart.value + datetime.timedelta(0, 300)
|
|
|
|
nooverlap_event.vevent.dtstart.value = nstartdate
|
|
|
|
nooverlap_event.vevent.dtend.value = nstartdate + datetime.timedelta(0, 3600)
|
|
|
|
nooverlap_event.vevent.uid.value = "recurring - overlap"
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, overlap_recurring_ics_name), nooverlap_event, exp_status=403)
|
2011-05-03 14:37:34 +02:00
|
|
|
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
def testRruleExceptionInvitationDance(self):
|
|
|
|
""" RRULE exception invitation dance """
|
|
|
|
|
|
|
|
# This workflow is based on what lightning 1.2.1 does
|
|
|
|
# create a reccurring event
|
|
|
|
# add an exception
|
|
|
|
# invite bob to the exception:
|
|
|
|
# bob is declined in the master event
|
|
|
|
# bob needs-action in the exception
|
|
|
|
# bob accepts
|
|
|
|
# bob is declined in the master event
|
|
|
|
# bob is accepted in the exception
|
|
|
|
# the organizer 'uninvites' bob
|
|
|
|
# the event disappears from bob's calendar
|
|
|
|
# bob isn't in the master+exception event
|
|
|
|
|
|
|
|
ics_name = "test-rrule-exception-invitation-dance.ics"
|
2012-06-22 21:06:17 +02:00
|
|
|
self.ics_list += [ics_name]
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar, ics_name), None)
|
|
|
|
self._deleteEvent(self.attendee1_client,
|
|
|
|
"%s%s" % (self.attendee1_calendar, ics_name), None)
|
|
|
|
|
|
|
|
# 1. create a recurring event in the organiser's calendar
|
|
|
|
summary="Test reccuring exception invite cancel"
|
|
|
|
uid="Test-recurring-exception-invite-cancel"
|
|
|
|
event = self._newEvent(summary, uid)
|
|
|
|
event.vevent.add('rrule').value = "FREQ=DAILY;COUNT=5"
|
|
|
|
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
|
|
|
|
|
|
|
# read the event back from the server
|
|
|
|
org_ev = self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
|
|
|
|
|
|
|
|
# 2. Add an exception to the master event and invite attendee1 to it
|
|
|
|
now = datetime.datetime.now(dateutil.tz.gettz("America/Montreal"))
|
|
|
|
org_ev.vevent.last_modified.value = now
|
|
|
|
orig_dtstart = org_ev.vevent.dtstart.value
|
|
|
|
orig_dtend = org_ev.vevent.dtend.value
|
|
|
|
|
|
|
|
ev_exception = org_ev.add("vevent")
|
|
|
|
ev_exception.add('created').value = now
|
|
|
|
ev_exception.add('last-modified').value = now
|
|
|
|
ev_exception.add('dtstamp').value = now
|
|
|
|
ev_exception.add('uid').value = uid
|
|
|
|
ev_exception.add('summary').value = summary
|
|
|
|
# out of laziness, add the exception for the first occurence of the event
|
|
|
|
recurrence_id = orig_dtstart
|
|
|
|
ev_exception.add('recurrence-id').value = recurrence_id
|
|
|
|
|
|
|
|
ev_exception.add('transp').value = "OPAQUE"
|
|
|
|
ev_exception.add('description').value = "Exception"
|
|
|
|
ev_exception.add('sequence').value = "1"
|
|
|
|
ev_exception.add('dtstart').value = orig_dtstart
|
|
|
|
ev_exception.add('dtend').value = orig_dtend
|
|
|
|
|
|
|
|
# 2.1 Add attendee1 and organizer to the exception
|
|
|
|
organizer = ev_exception.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.partstat_param = "ACCEPTED"
|
|
|
|
organizer.value = self.user_email
|
|
|
|
attendee = ev_exception.add('attendee')
|
|
|
|
attendee.cn_param = self.attendee1_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.role_param = "REQ-PARTICIPANT"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.attendee1_email
|
|
|
|
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), org_ev,
|
|
|
|
exp_status=204)
|
|
|
|
|
|
|
|
# 3. Make sure the attendee got the event
|
|
|
|
attendee_ev = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
|
|
|
|
|
|
|
|
for ev in attendee_ev.vevent_list:
|
|
|
|
try:
|
|
|
|
if (ev.recurrence_id.value):
|
|
|
|
attendee_ev_exception = ev
|
|
|
|
except:
|
|
|
|
attendee_ev_master = ev
|
|
|
|
|
|
|
|
# make sure sogo doesn't duplicate attendees - yes, we've seen that
|
|
|
|
self.assertEquals(len(attendee_ev_master.attendee_list), 1)
|
|
|
|
self.assertEquals(len(attendee_ev_exception.attendee_list), 1)
|
|
|
|
|
|
|
|
# 4. The master event must contain the invitation, declined
|
|
|
|
self.assertEquals(attendee_ev_master.attendee.partstat_param, "DECLINED")
|
|
|
|
|
|
|
|
# 5. The exception event contain the invitation, NEEDS-ACTION
|
|
|
|
self.assertEquals(attendee_ev_exception.attendee.partstat_param, "NEEDS-ACTION")
|
|
|
|
|
|
|
|
# 6. attendee accepts invitation
|
|
|
|
attendee_ev_exception.attendee.partstat_param = "ACCEPTED"
|
|
|
|
self._putEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name),
|
|
|
|
attendee_ev, exp_status=204)
|
|
|
|
|
|
|
|
# fetch the organizer's event
|
|
|
|
org_ev = self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
|
|
|
|
for ev in org_ev.vevent_list:
|
|
|
|
try:
|
|
|
|
if (ev.recurrence_id.value):
|
|
|
|
org_ev_exception = ev
|
|
|
|
except:
|
|
|
|
org_ev_master = ev
|
|
|
|
|
|
|
|
# make sure sogo doesn't duplicate attendees
|
|
|
|
self.assertEquals(len(org_ev_master.attendee_list), 1)
|
|
|
|
self.assertEquals(len(org_ev_exception.attendee_list), 1)
|
|
|
|
|
|
|
|
# 7. Make sure organizer got the accept for the exception and
|
|
|
|
# that the attendee is still declined in the master
|
|
|
|
self.assertEquals(org_ev_exception.attendee.partstat_param, "ACCEPTED")
|
|
|
|
self.assertEquals(org_ev_master.attendee.partstat_param, "DECLINED")
|
|
|
|
|
|
|
|
# 8. delete the attendee from the master event (uninvite)
|
|
|
|
# The event should be deleted from the attendee's calendar
|
|
|
|
del org_ev_exception.attendee
|
2012-05-11 22:01:06 +02:00
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name),
|
|
|
|
org_ev, exp_status=204)
|
2012-05-11 18:20:45 +02:00
|
|
|
del org_ev_master.attendee
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name),
|
|
|
|
org_ev, exp_status=204)
|
|
|
|
|
|
|
|
self._getEvent(self.client, "%s%s" % (self.attendee1_calendar, ics_name),
|
|
|
|
exp_status=404)
|
|
|
|
|
|
|
|
# now be happy
|
|
|
|
|
|
|
|
def testRruleInvitationDeleteExdate(self):
|
|
|
|
"""RRULE invitation delete exdate dance"""
|
|
|
|
|
|
|
|
# Workflow:
|
|
|
|
# Create an recurring event and invite Bob
|
|
|
|
# Add an exdate to the master event
|
|
|
|
# Verify that the exdate has propagated to Bob's calendar
|
|
|
|
# Add an exdate to bob's version of the event
|
|
|
|
# Verify that an exception has been created in the org's calendar
|
|
|
|
# and that bob is 'declined'
|
|
|
|
|
|
|
|
ics_name = "test-rrule-invitation-deleted-exdate-dance.ics"
|
2012-06-22 21:06:17 +02:00
|
|
|
self.ics_list += [ics_name]
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar, ics_name), None)
|
|
|
|
self._deleteEvent(self.attendee1_client,
|
|
|
|
"%s%s" % (self.attendee1_calendar, ics_name), None)
|
|
|
|
|
|
|
|
# 1. create a recurring event in the organiser's calendar
|
|
|
|
summary="Test-rrule-invitation-deleted-exdate-dance"
|
|
|
|
uid=summary
|
|
|
|
event = self._newEvent(summary, uid)
|
|
|
|
event.vevent.add('rrule').value = "FREQ=DAILY;COUNT=5"
|
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.partstat_param = "ACCEPTED"
|
|
|
|
organizer.value = self.user_email
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.attendee1_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.role_param = "REQ-PARTICIPANT"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.attendee1_email
|
|
|
|
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
|
|
|
|
|
|
|
# 2. Make sure the attendee got it
|
|
|
|
self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
|
|
|
|
|
|
|
|
# 3. Add exdate to master event
|
|
|
|
org_ev=self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
|
|
|
|
orig_dtstart = org_ev.vevent.dtstart.value
|
|
|
|
# exdate is a list in vobject.icalendar
|
|
|
|
org_exdate = [orig_dtstart.astimezone(dateutil.tz.gettz("UTC"))]
|
|
|
|
org_ev.vevent.add('exdate').value = org_exdate
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), org_ev, exp_status=204)
|
|
|
|
|
|
|
|
# 4. make sure the attendee has the exdate
|
|
|
|
attendee_ev = self._getEvent(self.attendee1_client, "%s%s" %
|
|
|
|
(self.attendee1_calendar, ics_name))
|
|
|
|
self.assertEqual(org_exdate, attendee_ev.vevent.exdate.value)
|
|
|
|
|
|
|
|
# 5. Create an exdate in the attendee's calendar
|
|
|
|
new_exdate = orig_dtstart + datetime.timedelta(days=2)
|
|
|
|
attendee_exdate = [new_exdate.astimezone(dateutil.tz.gettz("UTC"))]
|
|
|
|
attendee_ev.vevent.add('exdate').value = attendee_exdate
|
|
|
|
now = datetime.datetime.now(dateutil.tz.gettz("America/Montreal"))
|
|
|
|
attendee_ev.vevent.last_modified.value = now
|
|
|
|
self._putEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name),
|
|
|
|
attendee_ev, exp_status=204)
|
|
|
|
|
|
|
|
# 6. Make sure the attendee is:
|
|
|
|
# needs-action in master event
|
|
|
|
# declined in the new exception created by the exdate above
|
|
|
|
org_ev=self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
|
|
|
|
for ev in org_ev.vevent_list:
|
|
|
|
try:
|
|
|
|
if (ev.recurrence_id.value == attendee_exdate[0]):
|
|
|
|
org_ev_exception = ev
|
|
|
|
except:
|
|
|
|
org_ev_master = ev
|
|
|
|
|
|
|
|
self.assertTrue(org_ev_exception)
|
|
|
|
# make sure sogo doesn't duplicate attendees
|
|
|
|
self.assertEquals(len(org_ev_master.attendee_list), 1)
|
|
|
|
self.assertEquals(len(org_ev_exception.attendee_list), 1)
|
|
|
|
|
|
|
|
self.assertEqual(org_ev_master.attendee.partstat_param, "NEEDS-ACTION");
|
|
|
|
self.assertEqual(org_ev_exception.attendee.partstat_param, "DECLINED");
|
|
|
|
|
2012-06-20 22:34:28 +02:00
|
|
|
def testOrganizerIsAttendee(self):
|
2012-06-21 15:54:23 +02:00
|
|
|
""" iCal organizer is attendee - bug #1839 """
|
2012-06-20 22:34:28 +02:00
|
|
|
|
|
|
|
# This tries to have the same behavior as iCal
|
|
|
|
# 1. create an event, add an attendee and add the organizer as an attendee
|
|
|
|
# 2. SOGo should remove the organizer from the attendee list
|
|
|
|
ics_name = "test-organizer-is-attendee.ics"
|
2012-06-22 21:06:17 +02:00
|
|
|
self.ics_list += [ics_name]
|
|
|
|
|
2012-06-20 22:34:28 +02:00
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar, ics_name), None)
|
|
|
|
self._deleteEvent(self.attendee1_client,
|
|
|
|
"%s%s" % (self.attendee1_calendar, ics_name), None)
|
|
|
|
|
|
|
|
# 1. create a recurring event in the organiser's calendar
|
|
|
|
summary="org is attendee"
|
|
|
|
uid=summary
|
|
|
|
event = self._newEvent(summary, uid)
|
|
|
|
organizer = event.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.partstat_param = "ACCEPTED"
|
|
|
|
organizer.value = self.user_email
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.attendee1_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.role_param = "REQ-PARTICIPANT"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.attendee1_email
|
|
|
|
|
|
|
|
# 1.1 add the organizer as an attendee
|
|
|
|
attendee = event.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.user_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.role_param = "REQ-PARTICIPANT"
|
|
|
|
attendee.partstat_param = "ACCEPTED"
|
|
|
|
attendee.value = self.user_email
|
|
|
|
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
|
|
|
|
|
|
|
# 2. Fetch the event and make sure the organizer is not in the attendee list anymore
|
|
|
|
org_ev = self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
|
|
|
|
|
|
|
|
for attendee in org_ev.vevent.attendee_list:
|
|
|
|
self.assertNotEqual(self.user_email, attendee.value)
|
|
|
|
|
2012-06-27 18:06:20 +02:00
|
|
|
def testEventsWithSameUID(self):
|
|
|
|
""" PUT 2 events with the same UID - bug #1853 """
|
|
|
|
|
|
|
|
ics_name = "test-same-uid.ics"
|
|
|
|
self.ics_list += [ics_name]
|
|
|
|
|
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar, ics_name), None)
|
|
|
|
|
|
|
|
conflict_ics_name = "test-same-uid-conflict.ics"
|
|
|
|
self.ics_list += [ics_name]
|
|
|
|
|
|
|
|
self._deleteEvent(self.client,
|
|
|
|
"%s%s" % (self.user_calendar, conflict_ics_name), None)
|
|
|
|
|
|
|
|
# 1. create simple event
|
|
|
|
summary="same uid"
|
|
|
|
uid=summary
|
|
|
|
event = self._newEvent(summary, uid)
|
|
|
|
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
|
|
|
|
|
|
|
# PUT the same event with a new filename - should trigger a 403
|
|
|
|
self._putEvent(self.client, "%s%s" % (self.user_calendar, conflict_ics_name), event, exp_status=403)
|
|
|
|
|
2009-08-27 18:20:41 +02:00
|
|
|
def testInvitationDelegation(self):
|
|
|
|
""" invitation delegation """
|
|
|
|
|
2012-06-22 21:06:17 +02:00
|
|
|
ics_name = "test-delegation.ics"
|
|
|
|
self.ics_list += [ics_name]
|
|
|
|
|
2009-08-27 18:20:41 +02:00
|
|
|
# the invitation must not exist
|
|
|
|
self._deleteEvent(self.client,
|
2012-06-22 21:06:17 +02:00
|
|
|
"%s%s" % (self.user_calendar, ics_name), None)
|
2012-05-11 18:20:45 +02:00
|
|
|
self._deleteEvent(self.attendee1_client,
|
2012-06-22 21:06:17 +02:00
|
|
|
"%s%s" % (self.attendee1_calendar, ics_name), None)
|
2012-05-11 18:20:45 +02:00
|
|
|
self._deleteEvent(self.attendee1_delegate_client,
|
2012-06-22 21:06:17 +02:00
|
|
|
"%s%s" % (self.attendee1_delegate_calendar, ics_name), None)
|
2009-08-27 18:20:41 +02:00
|
|
|
|
|
|
|
# 1. org -> attendee => org: 1, attendee: 1 (pst=N-A), delegate: 0
|
|
|
|
|
|
|
|
invitation = self._newEvent()
|
|
|
|
invitation.add("method").value = "REQUEST"
|
|
|
|
organizer = invitation.vevent.add('organizer')
|
|
|
|
organizer.cn_param = self.user_name
|
|
|
|
organizer.value = self.user_email
|
|
|
|
attendee = invitation.vevent.add('attendee')
|
|
|
|
attendee.cn_param = self.attendee1_name
|
|
|
|
attendee.rsvp_param = "TRUE"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
attendee.value = self.attendee1_email
|
|
|
|
|
|
|
|
self._postEvent(self.client, self.user_calendar, invitation,
|
|
|
|
self.user_email, [self.attendee1_email])
|
|
|
|
del invitation.method
|
|
|
|
self._putEvent(self.client,
|
|
|
|
"%stest-delegation.ics" % self.user_calendar,
|
|
|
|
invitation)
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
att_inv = self._getEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics"
|
|
|
|
% self.attendee1_calendar)
|
|
|
|
self._compareAttendees(att_inv, invitation)
|
|
|
|
|
|
|
|
# 2. attendee delegates to delegate
|
|
|
|
# => org: 1 (updated), attendee: 1 (updated,pst=D),
|
|
|
|
# delegate: 1 (new,pst=N-A)
|
|
|
|
|
|
|
|
invitation.add("method").value = "REQUEST"
|
|
|
|
attendee1 = invitation.vevent.attendee
|
|
|
|
attendee1.partstat_param = "DELEGATED"
|
|
|
|
attendee1.delegated_to_param = self.attendee1_delegate_email
|
|
|
|
delegate = invitation.vevent.add('attendee')
|
|
|
|
delegate.delegated_from_param = self.attendee1_email
|
|
|
|
delegate.cn_param = self.attendee1_delegate_name
|
|
|
|
delegate.rsvp_param = "TRUE"
|
|
|
|
delegate.partstat_param = "NEEDS-ACTION"
|
|
|
|
delegate.value = self.attendee1_delegate_email
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
self._postEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
self.attendee1_calendar, invitation,
|
|
|
|
self.attendee1_email, [self.attendee1_delegate_email])
|
|
|
|
invitation.method.value = "REPLY"
|
2012-05-11 18:20:45 +02:00
|
|
|
self._postEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
self.attendee1_calendar, invitation,
|
|
|
|
self.attendee1_email, [self.user_email])
|
|
|
|
del invitation.method
|
2012-05-11 18:20:45 +02:00
|
|
|
self._putEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_calendar,
|
|
|
|
invitation, 204)
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
del_inv = self._getEvent(self.attendee1_delegate_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics"
|
|
|
|
% self.attendee1_delegate_calendar)
|
|
|
|
self._compareAttendees(del_inv, invitation)
|
|
|
|
org_inv = self._getEvent(self.client,
|
|
|
|
"%stest-delegation.ics" % self.user_calendar)
|
|
|
|
self._compareAttendees(org_inv, invitation)
|
|
|
|
|
|
|
|
# 3. delegate accepts
|
|
|
|
# => org: 1 (updated), attendee: 1 (updated,pst=D),
|
|
|
|
# delegate: 1 (accepted,pst=A)
|
|
|
|
|
|
|
|
invitation.add("method").value = "REPLY"
|
|
|
|
delegate.partstat_param = "ACCEPTED"
|
2012-05-11 18:20:45 +02:00
|
|
|
self._postEvent(self.attendee1_delegate_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
self.attendee1_delegate_calendar, invitation,
|
|
|
|
self.attendee1_delegate_email, [self.user_email, self.attendee1_email])
|
|
|
|
del invitation.method
|
2012-05-11 18:20:45 +02:00
|
|
|
self._putEvent(self.attendee1_delegate_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_delegate_calendar,
|
|
|
|
invitation, 204)
|
|
|
|
|
|
|
|
org_inv = self._getEvent(self.client,
|
|
|
|
"%stest-delegation.ics" % self.user_calendar)
|
|
|
|
self._compareAttendees(org_inv, invitation)
|
2012-05-11 18:20:45 +02:00
|
|
|
att_inv = self._getEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_calendar)
|
|
|
|
self._compareAttendees(att_inv, invitation)
|
|
|
|
|
|
|
|
# 4. attendee accepts
|
|
|
|
# => org: 1 (updated), attendee: 1 (updated,pst=A),
|
|
|
|
# delegate: 0 (cancelled, deleted)
|
|
|
|
|
|
|
|
cancellation = vobject.iCalendar()
|
|
|
|
cancellation.copy(invitation)
|
|
|
|
cancellation.add("method").value = "CANCEL"
|
|
|
|
cancellation.vevent.sequence.value = "1"
|
2012-05-11 18:20:45 +02:00
|
|
|
self._postEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
self.attendee1_calendar, cancellation,
|
|
|
|
self.attendee1_email, [self.attendee1_delegate_email])
|
|
|
|
|
|
|
|
attendee1 = invitation.vevent.attendee
|
|
|
|
attendee1.partstat_param = "ACCEPTED"
|
|
|
|
del attendee1.delegated_to_param
|
|
|
|
invitation.add("method").value = "REPLY"
|
|
|
|
invitation.vevent.remove(delegate)
|
2012-05-11 18:20:45 +02:00
|
|
|
self._postEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
self.attendee1_calendar, invitation,
|
|
|
|
self.attendee1_email, [self.user_email])
|
|
|
|
|
|
|
|
del invitation.method
|
2012-05-11 18:20:45 +02:00
|
|
|
self._putEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_calendar,
|
|
|
|
invitation, 204)
|
|
|
|
|
|
|
|
org_inv = self._getEvent(self.client,
|
|
|
|
"%stest-delegation.ics" % self.user_calendar)
|
|
|
|
self._compareAttendees(org_inv, invitation)
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
del_inv = self._getEvent(self.attendee1_delegate_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_delegate_calendar, 404)
|
|
|
|
|
|
|
|
# 5. org updates inv.
|
|
|
|
# => org: 1 (updated), attendee: 1 (updated), delegate: 0
|
|
|
|
|
|
|
|
invitation.add("method").value = "REQUEST"
|
|
|
|
invitation.vevent.summary.value = "Updated invitation"
|
|
|
|
invitation.vevent.sequence.value = "1"
|
|
|
|
attendee.partstat_param = "NEEDS-ACTION"
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
invitation.vevent.last_modified.value = now
|
|
|
|
invitation.vevent.dtstamp.value = now
|
|
|
|
|
|
|
|
self._postEvent(self.client, self.user_calendar, invitation,
|
|
|
|
self.user_email, [self.attendee1_email])
|
|
|
|
|
|
|
|
del invitation.method
|
|
|
|
self._putEvent(self.client,
|
|
|
|
"%stest-delegation.ics" % self.user_calendar,
|
|
|
|
invitation, 204)
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
att_inv = self._getEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_calendar)
|
|
|
|
self._compareAttendees(att_inv, invitation)
|
|
|
|
|
|
|
|
# 6. attendee delegates to delegate
|
|
|
|
# => org: 1 (updated), attendee: 1 (updated), delegate: 1 (new)
|
|
|
|
|
|
|
|
invitation.add("method").value = "REQUEST"
|
|
|
|
attendee1.partstat_param = "DELEGATED"
|
|
|
|
attendee1.delegated_to_param = self.attendee1_delegate_email
|
|
|
|
|
|
|
|
delegate = invitation.vevent.add('attendee')
|
|
|
|
delegate.delegated_from_param = self.attendee1_email
|
|
|
|
delegate.cn_param = self.attendee1_delegate_name
|
|
|
|
delegate.rsvp_param = "TRUE"
|
|
|
|
delegate.partstat_param = "NEEDS-ACTION"
|
|
|
|
delegate.value = self.attendee1_delegate_email
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
self._postEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
self.attendee1_calendar, invitation,
|
|
|
|
self.attendee1_email, [self.attendee1_delegate_email])
|
|
|
|
invitation.method.value = "REPLY"
|
2012-05-11 18:20:45 +02:00
|
|
|
self._postEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
self.attendee1_calendar, invitation,
|
|
|
|
self.attendee1_email, [self.user_email])
|
|
|
|
del invitation.method
|
2012-05-11 18:20:45 +02:00
|
|
|
self._putEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_calendar,
|
|
|
|
invitation, 204)
|
|
|
|
|
|
|
|
org_inv = self._getEvent(self.client,
|
|
|
|
"%stest-delegation.ics" % self.user_calendar)
|
|
|
|
self._compareAttendees(org_inv, invitation)
|
2012-05-11 18:20:45 +02:00
|
|
|
del_inv = self._getEvent(self.attendee1_delegate_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics"
|
|
|
|
% self.attendee1_delegate_calendar)
|
|
|
|
self._compareAttendees(del_inv, invitation)
|
|
|
|
|
|
|
|
# 7. delegate accepts
|
|
|
|
# => org: 1 (updated), attendee: 1 (updated), delegate: 1 (accepted)
|
|
|
|
|
|
|
|
invitation.add("method").value = "REPLY"
|
|
|
|
delegate.partstat_param = "ACCEPTED"
|
2012-05-11 18:20:45 +02:00
|
|
|
self._postEvent(self.attendee1_delegate_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
self.attendee1_delegate_calendar, invitation,
|
|
|
|
self.attendee1_delegate_email, [self.user_email,
|
|
|
|
self.attendee1_email])
|
|
|
|
del invitation.method
|
2012-05-11 18:20:45 +02:00
|
|
|
self._putEvent(self.attendee1_delegate_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_delegate_calendar,
|
|
|
|
invitation, 204)
|
|
|
|
|
|
|
|
org_inv = self._getEvent(self.client,
|
|
|
|
"%stest-delegation.ics" % self.user_calendar)
|
|
|
|
self._compareAttendees(org_inv, invitation)
|
2012-05-11 18:20:45 +02:00
|
|
|
att_inv = self._getEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_calendar)
|
|
|
|
self._compareAttendees(att_inv, invitation)
|
|
|
|
|
|
|
|
# 8. org updates inv.
|
|
|
|
# => org: 1 (updated), attendee: 1 (updated,partstat unchanged),
|
|
|
|
# delegate: 1 (updated,partstat reset)
|
|
|
|
|
|
|
|
invitation.add("method").value = "REQUEST"
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
invitation.vevent.last_modified.value = now
|
|
|
|
invitation.vevent.dtstamp.value = now
|
|
|
|
invitation.vevent.summary.value = "Updated invitation (again)"
|
|
|
|
invitation.vevent.sequence.value = "2"
|
|
|
|
delegate.partstat_param = "NEEDS-ACTION"
|
|
|
|
|
|
|
|
self._postEvent(self.client, self.user_calendar, invitation,
|
|
|
|
self.user_email, [self.attendee1_email, self.attendee1_delegate_email])
|
|
|
|
|
|
|
|
del invitation.method
|
|
|
|
self._putEvent(self.client,
|
|
|
|
"%stest-delegation.ics" % self.user_calendar,
|
|
|
|
invitation, 204)
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
att_inv = self._getEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_calendar)
|
|
|
|
self._compareAttendees(att_inv, invitation)
|
2012-05-11 18:20:45 +02:00
|
|
|
del_inv = self._getEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_calendar)
|
|
|
|
self._compareAttendees(del_inv, invitation)
|
|
|
|
|
|
|
|
# 9. org cancels invitation
|
|
|
|
# => org: 1 (updated), attendee: 0 (cancelled, deleted),
|
|
|
|
# delegate: 0 (cancelled, deleted)
|
|
|
|
|
|
|
|
invitation.add("method").value = "CANCEL"
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
invitation.vevent.last_modified.value = now
|
|
|
|
invitation.vevent.dtstamp.value = now
|
|
|
|
invitation.vevent.summary.value = "Cancelled invitation (again)"
|
|
|
|
invitation.vevent.sequence.value = "3"
|
|
|
|
|
|
|
|
self._postEvent(self.client, self.user_calendar, invitation,
|
|
|
|
self.user_email, [self.attendee1_email, self.attendee1_delegate_email])
|
|
|
|
|
|
|
|
del invitation.method
|
|
|
|
invitation.vevent.remove(attendee)
|
|
|
|
invitation.vevent.remove(delegate)
|
|
|
|
self._putEvent(self.client,
|
|
|
|
"%stest-delegation.ics" % self.user_calendar,
|
|
|
|
invitation, 204)
|
|
|
|
|
2012-05-11 18:20:45 +02:00
|
|
|
att_inv = self._getEvent(self.attendee1_client,
|
2009-08-27 18:20:41 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_calendar, 404)
|
2012-05-11 18:20:45 +02:00
|
|
|
del_inv = self._getEvent(self.attendee1_delegate_client,
|
2010-08-12 20:04:27 +02:00
|
|
|
"%stest-delegation.ics" % self.attendee1_delegate_calendar, 404)
|
2009-08-27 18:20:41 +02:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2010-07-13 18:02:56 +02:00
|
|
|
sogotests.runTests()
|