mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-05-11 06:25:31 +00:00
Monotone-Parent: 413f1a1eef0a131464297caa0b801dbd10e14b8d
Monotone-Revision: b2238fb6fffbf3d555c8ef5fd7436135fbbdfacb Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2009-08-27T16:20:41 Monotone-Branch: ca.inverse.sogo
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
2009-08-27 Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
|
||||
* Tests/test-caldav-scheduling.py: new set of tests for CalDAV
|
||||
scheduling (iTIP-over-DAV) operations. Implemented 9 scenarios for
|
||||
invitation delegation.
|
||||
|
||||
* SoObjects/Appointments/SOGoAppointmentObject.m
|
||||
(_updateAttendee:withDelegate:ownerUser:forEventUID:withRecurrenceId:):
|
||||
added "withDelegate:" parameter in order to be able to add or
|
||||
remove the delegate corresponding to the attendee delegation. We
|
||||
also adjust the "delegated-to:" and "delegated-from:" in the
|
||||
corresponding attendee element from the event copy.
|
||||
(-postCalDAVReplyTo:from:): deduce the delegate from the matching
|
||||
attendee and pass it as parameter to subsequent method calls.
|
||||
(-takeAttendeeStatus:withDelegate:from:withRecurrenceId:)
|
||||
(changeParticipationStatus:withDelegate:forRecurrenceId:): take a
|
||||
new "withDelegate:" parameter.
|
||||
|
||||
* Tests/webdavlib.py (HTTPQuery.__init__): the content-type is no
|
||||
longer passed as parameter and should be directly set by the
|
||||
client as an attribute.
|
||||
|
||||
@@ -47,11 +47,14 @@
|
||||
|
||||
@interface SOGoAppointmentObject : SOGoCalendarComponent
|
||||
|
||||
- (NSException *) changeParticipationStatus: (NSString *) _status;
|
||||
- (NSException *) changeParticipationStatus: (NSString *) _status
|
||||
- (NSException *) changeParticipationStatus: (NSString *) status
|
||||
withDelegate: (iCalPerson *) delegate;
|
||||
- (NSException *) changeParticipationStatus: (NSString *) status
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
forRecurrenceId: (NSCalendarDate *) _recurrenceId;
|
||||
|
||||
- (void) takeAttendeeStatus: (iCalPerson *) attendee
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
from: (SOGoUser *) originator
|
||||
withRecurrenceId: (NSCalendarDate*) recurrenceId;
|
||||
|
||||
|
||||
@@ -581,11 +581,13 @@
|
||||
// participation state has changed.
|
||||
// - uid is the actual UID of the user for whom we must
|
||||
// update the calendar event (with the participation change)
|
||||
// - delegate is the delegated attendee if any
|
||||
//
|
||||
// This method is called multiple times, in order to update the
|
||||
// status of the attendee in calendars for the particular event UID.
|
||||
//
|
||||
- (NSException *) _updateAttendee: (iCalPerson *) attendee
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
ownerUser: (SOGoUser *) theOwnerUser
|
||||
forEventUID: (NSString *) eventUID
|
||||
withRecurrenceId: (NSCalendarDate *) recurrenceId
|
||||
@@ -596,9 +598,10 @@
|
||||
SOGoAppointmentObject *eventObject;
|
||||
iCalCalendar *calendar;
|
||||
iCalEntityObject *event;
|
||||
iCalPerson *otherAttendee;
|
||||
NSString *iCalString, *recurrenceTime;
|
||||
iCalPerson *otherAttendee, *otherDelegate;
|
||||
NSString *iCalString, *recurrenceTime, *delegateEmail;
|
||||
NSException *error;
|
||||
BOOL addDelegate, removeDelegate;
|
||||
|
||||
error = nil;
|
||||
|
||||
@@ -623,15 +626,52 @@
|
||||
event = [eventObject newOccurenceWithID: recurrenceTime];
|
||||
}
|
||||
|
||||
if ([[event sequence] compare: sequence]
|
||||
== NSOrderedSame)
|
||||
if ([[event sequence] compare: sequence] == NSOrderedSame)
|
||||
{
|
||||
SOGoUser *currentUser;
|
||||
|
||||
currentUser = [context activeUser];
|
||||
otherAttendee = [event findParticipant: theOwnerUser];
|
||||
|
||||
delegateEmail = [otherAttendee delegatedTo];
|
||||
if ([delegateEmail length])
|
||||
delegateEmail = [delegateEmail substringFromIndex: 7];
|
||||
if ([delegateEmail length])
|
||||
otherDelegate = [event findParticipantWithEmail: delegateEmail];
|
||||
else
|
||||
otherDelegate = NO;
|
||||
|
||||
/* we handle the addition/deletion of delegated users */
|
||||
addDelegate = NO;
|
||||
removeDelegate = NO;
|
||||
if (delegate)
|
||||
{
|
||||
if (otherDelegate)
|
||||
{
|
||||
if (![delegate hasSameEmailAddress: otherDelegate])
|
||||
{
|
||||
removeDelegate = YES;
|
||||
addDelegate = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
addDelegate = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (otherDelegate)
|
||||
removeDelegate = YES;
|
||||
}
|
||||
|
||||
if (removeDelegate)
|
||||
[event removeFromAttendees: otherDelegate];
|
||||
if (addDelegate)
|
||||
[event addToAttendees: delegate];
|
||||
|
||||
[otherAttendee setPartStat: [attendee partStat]];
|
||||
|
||||
[otherAttendee setDelegatedTo: [attendee delegatedTo]];
|
||||
[otherAttendee setDelegatedFrom: [attendee delegatedFrom]];
|
||||
|
||||
// If one has accepted / declined an invitation on behalf of
|
||||
// the attendee, we add the user to the SENT-BY attribute.
|
||||
if (b && ![[currentUser login] isEqualToString: [theOwnerUser login]])
|
||||
@@ -663,12 +703,13 @@
|
||||
|
||||
|
||||
//
|
||||
// This method is invoked only from the SOGo Web interface.
|
||||
// This method is invoked from the SOGo Web interface.
|
||||
//
|
||||
// - theOwnerUser is owner of the calendar where the attendee
|
||||
// participation state has changed.
|
||||
//
|
||||
- (NSException *) _handleAttendee: (iCalPerson *) attendee
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
ownerUser: (SOGoUser *) theOwnerUser
|
||||
statusChange: (NSString *) newStatus
|
||||
inEvent: (iCalEvent *) event
|
||||
@@ -730,11 +771,12 @@
|
||||
if (organizerUID)
|
||||
// Update the attendee in organizer's calendar.
|
||||
ex = [self _updateAttendee: attendee
|
||||
ownerUser: theOwnerUser
|
||||
forEventUID: [event uid]
|
||||
withRecurrenceId: [event recurrenceId]
|
||||
withSequence: [event sequence]
|
||||
forUID: organizerUID
|
||||
withDelegate: delegate
|
||||
ownerUser: theOwnerUser
|
||||
forEventUID: [event uid]
|
||||
withRecurrenceId: [event recurrenceId]
|
||||
withSequence: [event sequence]
|
||||
forUID: organizerUID
|
||||
shouldAddSentBy: YES];
|
||||
}
|
||||
|
||||
@@ -748,26 +790,19 @@
|
||||
int i;
|
||||
|
||||
attendees = [event attendees];
|
||||
|
||||
for (i = 0; i < [attendees count]; i++)
|
||||
{
|
||||
att = [attendees objectAtIndex: i];
|
||||
|
||||
if (att == attendee) continue;
|
||||
|
||||
uid = [[LDAPUserManager sharedUserManager]
|
||||
getUIDForEmail: [att rfc822Email]];
|
||||
|
||||
if (uid)
|
||||
{
|
||||
[self _updateAttendee: attendee
|
||||
ownerUser: theOwnerUser
|
||||
forEventUID: [event uid]
|
||||
withRecurrenceId: [event recurrenceId]
|
||||
withSequence: [event sequence]
|
||||
forUID: uid
|
||||
shouldAddSentBy: YES];
|
||||
}
|
||||
uid = [att uid];
|
||||
if (uid && att != attendee)
|
||||
[self _updateAttendee: attendee
|
||||
withDelegate: delegate
|
||||
ownerUser: theOwnerUser
|
||||
forEventUID: [event uid]
|
||||
withRecurrenceId: [event recurrenceId]
|
||||
withSequence: [event sequence]
|
||||
forUID: uid
|
||||
shouldAddSentBy: YES];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1021,6 +1056,7 @@
|
||||
// be propagated to the organizer and the other attendees.
|
||||
//
|
||||
- (void) takeAttendeeStatus: (iCalPerson *) attendee
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
from: (SOGoUser *) ownerUser
|
||||
withRecurrenceId: (NSCalendarDate*) recurrenceId
|
||||
{
|
||||
@@ -1042,60 +1078,56 @@
|
||||
// If no occurence found, create one
|
||||
event = (iCalEvent*)[self newOccurenceWithID: recurrenceTime];
|
||||
}
|
||||
|
||||
|
||||
// Find attendee within event
|
||||
localAttendee = [event findParticipantWithEmail: [attendee rfc822Email]];
|
||||
if (localAttendee)
|
||||
{
|
||||
// Update the attendee's status
|
||||
#warning this code should probably not exist, as a REPLY POST will be followed \
|
||||
by a PUT
|
||||
[localAttendee setPartStat: [attendee partStat]];
|
||||
[localAttendee setDelegatedTo: [attendee delegatedTo]];
|
||||
[localAttendee setDelegatedFrom: [attendee delegatedFrom]];
|
||||
[self saveComponent: event];
|
||||
|
||||
|
||||
NSArray *attendees;
|
||||
iCalPerson *att;
|
||||
NSString *uid;
|
||||
int i;
|
||||
|
||||
// We update the copy of the organizer, only
|
||||
// if it's a local user.
|
||||
|
||||
/* We update the copy of the organizer, only if it's a local user. */
|
||||
#warning add a check for only local users
|
||||
uid = [[event organizer] uid];
|
||||
if (uid)
|
||||
[self _updateAttendee: attendee
|
||||
ownerUser: ownerUser
|
||||
forEventUID: [event uid]
|
||||
withRecurrenceId: [event recurrenceId]
|
||||
withSequence: [event sequence]
|
||||
forUID: uid
|
||||
withDelegate: delegate
|
||||
ownerUser: ownerUser
|
||||
forEventUID: [event uid]
|
||||
withRecurrenceId: [event recurrenceId]
|
||||
withSequence: [event sequence]
|
||||
forUID: uid
|
||||
shouldAddSentBy: NO];
|
||||
|
||||
attendees = [event attendees];
|
||||
|
||||
attendees = [event attendees];
|
||||
for (i = 0; i < [attendees count]; i++)
|
||||
{
|
||||
att = [attendees objectAtIndex: i];
|
||||
|
||||
if (att == attendee) continue;
|
||||
|
||||
uid = [[LDAPUserManager sharedUserManager]
|
||||
getUIDForEmail: [att rfc822Email]];
|
||||
|
||||
if (uid)
|
||||
{
|
||||
// We skip the update that correspond to the owner
|
||||
// since the CalDAV client will already have updated
|
||||
// the actual event.
|
||||
if ([ownerUser hasEmail: [att rfc822Email]])
|
||||
continue;
|
||||
|
||||
[self _updateAttendee: attendee
|
||||
ownerUser: ownerUser
|
||||
forEventUID: [event uid]
|
||||
withRecurrenceId: [event recurrenceId]
|
||||
withSequence: [event sequence]
|
||||
forUID: uid
|
||||
shouldAddSentBy: NO];
|
||||
}
|
||||
uid = [att uid];
|
||||
if (uid
|
||||
&& !(att == attendee || att == delegate
|
||||
/* We skip the update that correspond to the owner since
|
||||
the CalDAV client will already have updated the actual
|
||||
event. */
|
||||
|| [ownerUser hasEmail: [att rfc822Email]]))
|
||||
[self _updateAttendee: attendee
|
||||
withDelegate: delegate
|
||||
ownerUser: ownerUser
|
||||
forEventUID: [event uid]
|
||||
withRecurrenceId: [event recurrenceId]
|
||||
withSequence: [event sequence]
|
||||
forUID: uid
|
||||
shouldAddSentBy: NO];
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1107,9 +1139,9 @@
|
||||
{
|
||||
NSMutableArray *elements;
|
||||
NSEnumerator *recipientsEnum;
|
||||
NSString *recipient, *uid, *eventUID;
|
||||
NSString *recipient, *uid, *eventUID, *delegateEmail;
|
||||
iCalEvent *event;
|
||||
iCalPerson *attendee, *person;
|
||||
iCalPerson *attendee, *person, *delegate;
|
||||
SOGoAppointmentObject *recipientEvent;
|
||||
SOGoUser *ownerUser;
|
||||
|
||||
@@ -1121,6 +1153,16 @@
|
||||
attendee = [event findParticipant: ownerUser];
|
||||
eventUID = [event uid];
|
||||
|
||||
delegate = nil;
|
||||
delegateEmail = [attendee delegatedTo];
|
||||
if ([delegateEmail length])
|
||||
{
|
||||
delegateEmail = [delegateEmail substringFromIndex: 7];
|
||||
if ([delegateEmail length])
|
||||
delegate
|
||||
= [event findParticipantWithEmail: delegateEmail];
|
||||
}
|
||||
|
||||
recipientsEnum = [recipients objectEnumerator];
|
||||
while ((recipient = [recipientsEnum nextObject]))
|
||||
if ([[recipient lowercaseString] hasPrefix: @"mailto:"])
|
||||
@@ -1134,9 +1176,10 @@
|
||||
if ([recipientEvent isNew])
|
||||
[recipientEvent saveComponent: event];
|
||||
else
|
||||
[recipientEvent takeAttendeeStatus: attendee
|
||||
from: ownerUser
|
||||
withRecurrenceId: [event recurrenceId]];
|
||||
[recipientEvent takeAttendeeStatus: attendee
|
||||
withDelegate: delegate
|
||||
from: ownerUser
|
||||
withRecurrenceId: [event recurrenceId]];
|
||||
}
|
||||
|
||||
// Send reply to recipient/organizer
|
||||
@@ -1154,12 +1197,15 @@
|
||||
//
|
||||
// This method is invoked only from the SOGo Web interface.
|
||||
//
|
||||
- (NSException *) changeParticipationStatus: (NSString *) _status
|
||||
- (NSException *) changeParticipationStatus: (NSString *) status
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
{
|
||||
return [self changeParticipationStatus: _status forRecurrenceId: nil];
|
||||
return [self changeParticipationStatus: status withDelegate: delegate
|
||||
forRecurrenceId: nil];
|
||||
}
|
||||
|
||||
- (NSException *) changeParticipationStatus: (NSString *) _status
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
forRecurrenceId: (NSCalendarDate *) _recurrenceId
|
||||
{
|
||||
iCalCalendar *calendar;
|
||||
@@ -1193,19 +1239,20 @@
|
||||
}
|
||||
if (event)
|
||||
{
|
||||
// owerUser will actually be the owner of the calendar
|
||||
// ownerUser will actually be the owner of the calendar
|
||||
// where the participation change on the event has
|
||||
// actually occured. The particpation change will of
|
||||
// course be on the attendee that is the owner of the
|
||||
// calendar where the participation change has occured.
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
|
||||
|
||||
attendee = [event findParticipant: ownerUser];
|
||||
if (attendee)
|
||||
ex = [self _handleAttendee: attendee
|
||||
ownerUser: ownerUser
|
||||
statusChange: _status
|
||||
inEvent: event];
|
||||
withDelegate: delegate
|
||||
ownerUser: ownerUser
|
||||
statusChange: _status
|
||||
inEvent: event];
|
||||
else
|
||||
ex = [NSException exceptionWithHTTPStatus: 404 // Not Found
|
||||
reason: @"user does not participate in this "
|
||||
@@ -1268,7 +1315,8 @@
|
||||
else if ([occurence userIsParticipant: ownerUser])
|
||||
// The current user deletes the occurence; let the organizer know that
|
||||
// the user has declined this occurence.
|
||||
[self changeParticipationStatus: @"DECLINED" forRecurrenceId: recurrenceId];
|
||||
[self changeParticipationStatus: @"DECLINED" withDelegate: nil
|
||||
forRecurrenceId: recurrenceId];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
from: (SOGoUser *) from
|
||||
to: (iCalPerson *) recipient;
|
||||
- (void) sendResponseToOrganizer: (iCalRepeatableEntityObject *) newComponent
|
||||
from: (SOGoUser *) owner;
|
||||
from: (SOGoUser *) owner;
|
||||
|
||||
- (void) sendReceiptEmailUsingTemplateNamed: (NSString *) template
|
||||
forObject: (iCalRepeatableEntityObject *) object
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
@class NSException;
|
||||
|
||||
@class iCalCalendar;
|
||||
@class iCalPerson;
|
||||
@class iCalRepeatableEntityObject;
|
||||
@class SOGoCalendarComponent;
|
||||
|
||||
@@ -55,7 +56,8 @@
|
||||
- (void) setMasterComponent: (iCalRepeatableEntityObject *) newMaster;
|
||||
- (void) setIsNew: (BOOL) newIsNew;
|
||||
|
||||
- (NSException *) changeParticipationStatus: (NSString *) newPartStat;
|
||||
- (NSException *) changeParticipationStatus: (NSString *) newPartStat
|
||||
withDelegate: (iCalPerson *) delegate;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -194,12 +194,15 @@
|
||||
#warning most of SOGoCalendarComponent and SOGoComponentOccurence share the same external interface... \
|
||||
they should be siblings or SOGoComponentOccurence the parent class of SOGoCalendarComponent...
|
||||
- (NSException *) changeParticipationStatus: (NSString *) newStatus
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
{
|
||||
NSCalendarDate *date;
|
||||
|
||||
date = [component recurrenceId];
|
||||
|
||||
return [container changeParticipationStatus: newStatus forRecurrenceId: date];
|
||||
return [container changeParticipationStatus: newStatus
|
||||
withDelegate: delegate
|
||||
forRecurrenceId: date];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -5,3 +5,6 @@ password = "mypass"
|
||||
|
||||
subscriber_username = "otheruser"
|
||||
subscriber_password = "otherpass"
|
||||
|
||||
attendee1 = "user@domain.com"
|
||||
attendee1_delegate = "otheruser@domain.com"
|
||||
|
||||
Executable
+416
@@ -0,0 +1,416 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# setup: username must be super-user or have read-access to PUBLIC events in
|
||||
# both attendee and delegate's personal calendar
|
||||
|
||||
from config import hostname, port, username, password, attendee1, attendee1_delegate
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
import vobject
|
||||
import vobject.base
|
||||
import vobject.icalendar
|
||||
import webdavlib
|
||||
import StringIO
|
||||
|
||||
def fetchUserInfo(login):
|
||||
client = webdavlib.WebDAVClient(hostname, port, username, password)
|
||||
resource = "/SOGo/dav/%s/" % login
|
||||
propfind = webdavlib.WebDAVPROPFIND(resource,
|
||||
["displayname",
|
||||
"{urn:ietf:params:xml:ns:caldav}calendar-user-address-set"],
|
||||
0)
|
||||
propfind.xpath_namespace = { "D": "DAV:",
|
||||
"C": "urn:ietf:params:xml:ns:caldav" }
|
||||
client.execute(propfind)
|
||||
assert(propfind.response["status"] == 207)
|
||||
name_nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:displayname',
|
||||
None)
|
||||
email_nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set/D:href',
|
||||
None)
|
||||
|
||||
return (name_nodes[0].childNodes[0].nodeValue, email_nodes[0].childNodes[0].nodeValue)
|
||||
|
||||
class CalDAVITIPDelegationTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = webdavlib.WebDAVClient(hostname, port,
|
||||
username, password)
|
||||
(self.user_name, self.user_email) = fetchUserInfo(username)
|
||||
(self.attendee1_name, self.attendee1_email) = fetchUserInfo(attendee1)
|
||||
(self.attendee1_delegate_name, self.attendee1_delegate_email) = fetchUserInfo(attendee1_delegate)
|
||||
|
||||
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
|
||||
|
||||
def _newEvent(self):
|
||||
newCal = vobject.iCalendar()
|
||||
vevent = newCal.add('vevent')
|
||||
vevent.add('summary').value = "test event"
|
||||
vevent.add('transp').value = "OPAQUE"
|
||||
|
||||
now = datetime.datetime.now()
|
||||
startdate = vevent.add('dtstart')
|
||||
startdate.value = now
|
||||
enddate = vevent.add('dtend')
|
||||
enddate.value = now + datetime.timedelta(0, 3600)
|
||||
vevent.add('uid').value = "test-delegation"
|
||||
vevent.add('dtstamp').value = now
|
||||
vevent.add('last-modified').value = now
|
||||
vevent.add('created').value = now
|
||||
|
||||
vevent.add('sequence').value = "0"
|
||||
|
||||
return newCal
|
||||
|
||||
def tearDown(self):
|
||||
self._deleteEvent(self.client,
|
||||
"%stest-delegation.ics" % self.user_calendar, None)
|
||||
self._deleteEvent(self.client,
|
||||
"%stest-delegation.ics" % self.attendee1_calendar, None)
|
||||
self._deleteEvent(self.client,
|
||||
"%stest-delegation.ics" % self.attendee1_delegate_calendar,
|
||||
None)
|
||||
|
||||
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)
|
||||
|
||||
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]))
|
||||
|
||||
def testInvitationDelegation(self):
|
||||
""" invitation delegation """
|
||||
|
||||
# the invitation must not exist
|
||||
self._deleteEvent(self.client,
|
||||
"%stest-delegation.ics" % self.user_calendar, None)
|
||||
self._deleteEvent(self.client,
|
||||
"%stest-delegation.ics" % self.attendee1_calendar, None)
|
||||
self._deleteEvent(self.client,
|
||||
"%stest-delegation.ics" % self.attendee1_delegate_calendar,
|
||||
None)
|
||||
|
||||
# 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)
|
||||
|
||||
att_inv = self._getEvent(self.client,
|
||||
"%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
|
||||
|
||||
self._postEvent(self.client,
|
||||
self.attendee1_calendar, invitation,
|
||||
self.attendee1_email, [self.attendee1_delegate_email])
|
||||
invitation.method.value = "REPLY"
|
||||
self._postEvent(self.client,
|
||||
self.attendee1_calendar, invitation,
|
||||
self.attendee1_email, [self.user_email])
|
||||
del invitation.method
|
||||
self._putEvent(self.client,
|
||||
"%stest-delegation.ics" % self.attendee1_calendar,
|
||||
invitation, 204)
|
||||
|
||||
del_inv = self._getEvent(self.client,
|
||||
"%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"
|
||||
self._postEvent(self.client,
|
||||
self.attendee1_delegate_calendar, invitation,
|
||||
self.attendee1_delegate_email, [self.user_email, self.attendee1_email])
|
||||
del invitation.method
|
||||
self._putEvent(self.client,
|
||||
"%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)
|
||||
att_inv = self._getEvent(self.client,
|
||||
"%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"
|
||||
self._postEvent(self.client,
|
||||
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)
|
||||
self._postEvent(self.client,
|
||||
self.attendee1_calendar, invitation,
|
||||
self.attendee1_email, [self.user_email])
|
||||
|
||||
del invitation.method
|
||||
self._putEvent(self.client,
|
||||
"%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)
|
||||
|
||||
del_inv = self._getEvent(self.client,
|
||||
"%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)
|
||||
|
||||
att_inv = self._getEvent(self.client,
|
||||
"%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
|
||||
|
||||
self._postEvent(self.client,
|
||||
self.attendee1_calendar, invitation,
|
||||
self.attendee1_email, [self.attendee1_delegate_email])
|
||||
invitation.method.value = "REPLY"
|
||||
self._postEvent(self.client,
|
||||
self.attendee1_calendar, invitation,
|
||||
self.attendee1_email, [self.user_email])
|
||||
del invitation.method
|
||||
self._putEvent(self.client,
|
||||
"%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)
|
||||
del_inv = self._getEvent(self.client,
|
||||
"%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"
|
||||
self._postEvent(self.client,
|
||||
self.attendee1_delegate_calendar, invitation,
|
||||
self.attendee1_delegate_email, [self.user_email,
|
||||
self.attendee1_email])
|
||||
del invitation.method
|
||||
self._putEvent(self.client,
|
||||
"%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)
|
||||
att_inv = self._getEvent(self.client,
|
||||
"%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)
|
||||
|
||||
att_inv = self._getEvent(self.client,
|
||||
"%stest-delegation.ics" % self.attendee1_calendar)
|
||||
self._compareAttendees(att_inv, invitation)
|
||||
del_inv = self._getEvent(self.client,
|
||||
"%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)
|
||||
|
||||
att_inv = self._getEvent(self.client,
|
||||
"%stest-delegation.ics" % self.attendee1_calendar, 404)
|
||||
del_inv = self._getEvent(self.client,
|
||||
"%stest-delegation.ics" % self.attendee1_calendar, 404)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -516,14 +516,16 @@
|
||||
|
||||
- (id) acceptAction
|
||||
{
|
||||
[[self clientObject] changeParticipationStatus: @"ACCEPTED"];
|
||||
[[self clientObject] changeParticipationStatus: @"ACCEPTED"
|
||||
withDelegate: nil];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) declineAction
|
||||
{
|
||||
[[self clientObject] changeParticipationStatus: @"DECLINED"];
|
||||
[[self clientObject] changeParticipationStatus: @"DECLINED"
|
||||
withDelegate: nil];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user