[CalendarServer-changes] [13591] CalendarServer/branches/release/CalendarServer-5.3-dev

source_changes at macosforge.org source_changes at macosforge.org
Sun Jun 1 19:47:48 PDT 2014


Revision: 13591
          http://trac.calendarserver.org//changeset/13591
Author:   cdaboo at apple.com
Date:     2014-06-01 19:47:48 -0700 (Sun, 01 Jun 2014)
Log Message:
-----------
Merge schedule-agent fix from trunk.

Modified Paths:
--------------
    CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/ical.py
    CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_icalendar.py
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/cuaddress.py
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/implicit.py
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_implicit.py

Added Paths:
-----------
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_processing.py

Removed Paths:
-------------
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/ical.py	2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/ical.py	2014-06-02 02:47:48 UTC (rev 13591)
@@ -2156,6 +2156,26 @@
         return is_server
 
 
+    def cleanOrganizerScheduleAgent(self):
+        """
+        Remove components whose ORGANIZER property does not have
+        SCHEDULE-AGENT=SERVER.
+        """
+
+        changed = False
+        for component in tuple(self.subcomponents()):
+            if component.name() in ignoredComponents:
+                continue
+
+            organizerProp = component.getOrganizerProperty()
+            if organizerProp is not None:
+                if organizerProp.parameterValue("SCHEDULE-AGENT", "SERVER") != "SERVER":
+                    self.removeComponent(component)
+                    changed = True
+
+        return changed
+
+
     def getAttendees(self):
         """
         Get the attendee value. Works on either a VCALENDAR or on a component.

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_icalendar.py	2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_icalendar.py	2014-06-02 02:47:48 UTC (rev 13591)
@@ -10429,3 +10429,163 @@
             changed = component.hasDuplicatePrivateComments(doFix=True)
             self.assertEqual(sorted(normalize_iCalStr(component).splitlines()), sorted(normalize_iCalStr(result).splitlines()), msg=title)
             self.assertEqual(changed, result_changed, msg=title)
+
+
+    def test_cleanOrganizerScheduleAgent(self):
+        """
+        Test that L{Component.cleanOrganizerScheduleAgent} correctly removes components.
+        """
+
+        data = (
+            (
+                "All SERVER - master only",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "All SERVER - master and overrides",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            (
+                "Master CLIENT and override SERVER",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER;SCHEDULE-AGENT=CLIENT:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+        )
+
+        for title, txt, result, result_changed in data:
+            component = Component.fromString(txt)
+            changed = component.cleanOrganizerScheduleAgent()
+            self.assertEqual(sorted(normalize_iCalStr(component).splitlines()), sorted(normalize_iCalStr(result).splitlines()), msg=title)
+            self.assertEqual(changed, result_changed, msg=title)

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/cuaddress.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/cuaddress.py	2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/cuaddress.py	2014-06-02 02:47:48 UTC (rev 13591)
@@ -38,7 +38,14 @@
         self.serviceType = None
 
 
+    def hosted(self):
+        """
+        Is this user hosted on this service (this pod or any other)
+        """
+        return False
 
+
+
 class LocalCalendarUser(CalendarUser):
 
     def __init__(self, cuaddr, principal, inbox=None):
@@ -52,7 +59,14 @@
         return "Local calendar user: %s" % (self.cuaddr,)
 
 
+    def hosted(self):
+        """
+        Is this user hosted on this service (this pod or any other)
+        """
+        return True
 
+
+
 class PartitionedCalendarUser(CalendarUser):
 
     def __init__(self, cuaddr, principal):
@@ -65,7 +79,14 @@
         return "Partitioned calendar user: %s" % (self.cuaddr,)
 
 
+    def hosted(self):
+        """
+        Is this user hosted on this service (this pod or any other)
+        """
+        return True
 
+
+
 class OtherServerCalendarUser(CalendarUser):
 
     def __init__(self, cuaddr, principal):
@@ -78,7 +99,14 @@
         return "Other server calendar user: %s" % (self.cuaddr,)
 
 
+    def hosted(self):
+        """
+        Is this user hosted on this service (this pod or any other)
+        """
+        return True
 
+
+
 class RemoteCalendarUser(CalendarUser):
 
     def __init__(self, cuaddr):

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/implicit.py	2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/implicit.py	2014-06-02 02:47:48 UTC (rev 13591)
@@ -141,6 +141,7 @@
 
             # If the new data has no organizer, then there must also be no attendees
             if self.organizer is None and self.attendees:
+                log.error("organizer-allowed: Organizer removal also requires attendees to be removed for UID: %s" % (self.uid,))
                 raise HTTPError(ErrorResponse(
                     responsecode.FORBIDDEN,
                     (caldav_namespace, "organizer-allowed"),
@@ -163,6 +164,11 @@
             (existing_type != new_type) and
             existing_resource
         ):
+            log.error("valid-attendee-change: Cannot change scheduling object mode for %s to %s for UID: %s" % (
+                existing_type,
+                new_type,
+                self.uid
+            ))
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
                 (caldav_namespace, "valid-attendee-change"),
@@ -171,6 +177,7 @@
 
         # Organizer events must have a master component
         if self.state == "organizer" and self.calendar.masterComponent() is None:
+            log.error("organizer-allowed: Organizer cannot schedule without a master component for UID: %s" % (self.uid,))
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
                 (caldav_namespace, "organizer-allowed"),
@@ -251,7 +258,7 @@
         # to create new scheduling resources.
         if self.action == "create":
             if self.organizerPrincipal and not self.organizerPrincipal.enabledAsOrganizer():
-                log.error("ORGANIZER not allowed to be an Organizer: %s" % (self.organizer,))
+                log.error("organizer-allowed: ORGANIZER not allowed to be an Organizer: %s" % (self.organizer,))
                 raise HTTPError(ErrorResponse(
                     responsecode.FORBIDDEN,
                     (caldav_namespace, "organizer-allowed"),
@@ -427,7 +434,7 @@
             self.organizer = self.calendar.validOrganizerForScheduling()
         except ValueError:
             # We have different ORGANIZERs in the same iCalendar object - this is an error
-            log.error("Only one ORGANIZER is allowed in an iCalendar object:\n%s" % (self.calendar,))
+            log.error("single-organizer: Only one ORGANIZER is allowed in an iCalendar object:\n%s" % (self.calendar,))
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
                 (caldav_namespace, "single-organizer"),
@@ -715,7 +722,7 @@
                 oldOrganizer = _normalizeCUAddress(self.oldcalendar.getOrganizer())
                 newOrganizer = _normalizeCUAddress(self.calendar.getOrganizer())
                 if oldOrganizer != newOrganizer:
-                    log.error("Cannot change ORGANIZER: UID:%s" % (self.uid,))
+                    log.error("valid-organizer-change: Cannot change ORGANIZER: UID:%s" % (self.uid,))
                     raise HTTPError(ErrorResponse(
                         responsecode.FORBIDDEN,
                         (caldav_namespace, "valid-organizer-change"),
@@ -1118,7 +1125,7 @@
                 oldOrganizer = self.oldcalendar.getOrganizer()
                 newOrganizer = self.calendar.getOrganizer()
                 if oldOrganizer != newOrganizer:
-                    log.error("Cannot change ORGANIZER: UID:%s" % (self.uid,))
+                    log.error("valid-attendee-change: Cannot change ORGANIZER: UID:%s" % (self.uid,))
                     raise HTTPError(ErrorResponse(
                         responsecode.FORBIDDEN,
                         (caldav_namespace, "valid-attendee-change"),
@@ -1133,13 +1140,24 @@
 
                 # If Organizer copy exists we cannot allow SCHEDULE-AGENT=CLIENT or NONE
                 if not doScheduling:
-                    log.error("Attendee '%s' is not allowed to change SCHEDULE-AGENT on organizer: UID:%s" % (self.attendeePrincipal, self.uid,))
-                    raise HTTPError(ErrorResponse(
-                        responsecode.FORBIDDEN,
-                        (caldav_namespace, "valid-attendee-change"),
-                        "Cannot alter organizer",
-                    ))
+                    # If an existing resource is present and it does not have SCHEDULE-AGENT=SERVER, then
+                    # try and fix the situation by using the organizer's copy of the event and stripping
+                    # the incoming attendee copy of any SCHEDULE-AGENT=CLIENT components. That should allow
+                    # a fixed version of the data to be stored and proper scheduling to occur.
+                    if self.oldcalendar is not None and not self.oldcalendar.getOrganizerScheduleAgent():
+                        self.oldcalendar = self.organizer_calendar.duplicate()
+                        self.oldcalendar.attendeesView((self.attendee,), onlyScheduleAgentServer=True)
+                        self.calendar.cleanOrganizerScheduleAgent()
+                        doScheduling = True
 
+                    if not doScheduling:
+                        log.error("valid-attendee-change: Attendee '%s' is not allowed to change SCHEDULE-AGENT on organizer: UID:%s" % (self.attendeePrincipal, self.uid,))
+                        raise HTTPError(ErrorResponse(
+                            responsecode.FORBIDDEN,
+                            (caldav_namespace, "valid-attendee-change"),
+                            "Cannot alter organizer",
+                        ))
+
                 # Determine whether the current change is allowed
                 changeAllowed, doITipReply, changedRids, newCalendar = self.isAttendeeChangeInsignificant()
                 if changeAllowed:
@@ -1151,7 +1169,7 @@
                         self.return_status = ImplicitScheduler.STATUS_ORPHANED_EVENT
                         returnValue(None)
                     else:
-                        log.error("Attendee '%s' is not allowed to make an unauthorized change to an organized event: UID:%s" % (self.attendeePrincipal, self.uid,))
+                        log.error("valid-attendee-change: Attendee '%s' is not allowed to make an unauthorized change to an organized event: UID:%s" % (self.attendeePrincipal, self.uid,))
                         raise HTTPError(ErrorResponse(
                             responsecode.FORBIDDEN,
                             (caldav_namespace, "valid-attendee-change"),
@@ -1183,7 +1201,7 @@
                         if self.oldcalendar:
                             oldScheduling = self.oldcalendar.getOrganizerScheduleAgent()
                             if not oldScheduling:
-                                log.error("Attendee '%s' is not allowed to set SCHEDULE-AGENT=SERVER on organizer: UID:%s" % (self.attendeePrincipal, self.uid,))
+                                log.error("valid-attendee-change: Attendee '%s' is not allowed to set SCHEDULE-AGENT=SERVER on organizer: UID:%s" % (self.attendeePrincipal, self.uid,))
                                 raise HTTPError(ErrorResponse(
                                     responsecode.FORBIDDEN,
                                     (caldav_namespace, "valid-attendee-change"),
@@ -1240,7 +1258,7 @@
                 oldOrganizer = self.oldcalendar.getOrganizer()
                 newOrganizer = self.calendar.getOrganizer()
                 if oldOrganizer != newOrganizer and self.oldcalendar.getOrganizerScheduleAgent():
-                    log.error("Cannot change ORGANIZER: UID:%s" % (self.uid,))
+                    log.error("valid-attendee-change: Cannot change ORGANIZER: UID:%s" % (self.uid,))
                     raise HTTPError(ErrorResponse(
                         responsecode.FORBIDDEN,
                         (caldav_namespace, "valid-attendee-change"),
@@ -1260,7 +1278,7 @@
                             break
 
                     if found_old:
-                        log.error("Cannot remove ATTENDEE: UID:%s" % (self.uid,))
+                        log.error("valid-attendee-change: Cannot remove ATTENDEE: UID:%s" % (self.uid,))
                         raise HTTPError(ErrorResponse(
                             responsecode.FORBIDDEN,
                             (caldav_namespace, "valid-attendee-change"),

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py	2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py	2014-06-02 02:47:48 UTC (rev 13591)
@@ -460,8 +460,11 @@
             new_organizer = normalizeCUAddr(self.message.getOrganizer())
             new_organizer = normalizeCUAddr(new_organizer) if new_organizer else ""
             if existing_organizer != new_organizer:
-                log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring UID: '%s' - organizer has no copy" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
-                raise ImplicitProcessorException("5.3;Organizer change not allowed")
+                # Additional check - if the existing organizer is missing and the originator
+                # is local to the server - then allow the change
+                if not (existing_organizer == "" and self.originator.hosted()):
+                    log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring UID: '%s' - organizer has no copy" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                    raise ImplicitProcessorException("5.3;Organizer change not allowed")
 
         # Handle splitting of data early so we can preserve per-attendee data
         if self.message.hasProperty("X-CALENDARSERVER-SPLIT-OLDER-UID"):

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_implicit.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_implicit.py	2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_implicit.py	2014-06-02 02:47:48 UTC (rev 13591)
@@ -1624,3 +1624,324 @@
         self.assertEqual(len(list2), 2)
         self.assertTrue(list2[0].startswith(hashlib.md5("12345-67890").hexdigest()))
         self.assertTrue(list2[1].startswith(hashlib.md5("12345-67890").hexdigest()))
+
+
+
+class ScheduleAgentFixBase(CommonCommonTests, TestCase):
+    """
+    Test txdav.caldav.datastore.scheduling.implicit.
+    """
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(ScheduleAgentFixBase, self).setUp()
+        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
+        yield self.populate()
+        self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", 0)
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+        self.notifierFactory.reset()
+
+
+    def storeUnderTest(self):
+        """
+        Create and return a L{CalendarStore} for testing.
+        """
+        return self._sqlCalendarStore
+
+    metadata = {
+        "accessMode": "PUBLIC",
+        "isScheduleObject": True,
+        "scheduleTag": "abc",
+        "scheduleEtags": (),
+        "hasPrivateComment": False,
+    }
+
+    @classproperty(cache=False)
+    def requirements(cls): #@NoSelf
+        return {
+        "user01": {
+            "calendar_1": {
+                "organizer.ics": (cls.organizer_data, cls.metadata),
+            },
+            "inbox": {
+            },
+        },
+        "user02": {
+            "calendar_1": {
+                "attendee2.ics": (cls.attendee2_data, cls.metadata),
+            },
+            "inbox": {
+            },
+        },
+        "user03": {
+            "calendar_1": {
+                "attendee3.ics": (cls.attendee3_data, cls.metadata),
+            },
+            "inbox": {
+            },
+        },
+    }
+
+
+
+class ScheduleAgentFix(ScheduleAgentFixBase):
+    """
+    Test that implicit scheduling where an attendee has S-A=CLIENT and S-A=SERVER is
+    corrected when the attendee updates.
+    """
+
+    organizer_data = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+RECURRENCE-ID:20140102T100000Z
+DTSTART:20140102T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+"""
+
+    attendee2_data = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER;SCHEDULE-AGENT=CLIENT:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+RECURRENCE-ID:20140102T100000Z
+DTSTART:20140102T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER;SCHEDULE-AGENT=SERVER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+"""
+
+    attendee2_update_data = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER;SCHEDULE-AGENT=CLIENT:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+RECURRENCE-ID:20140102T100000Z
+DTSTART:20140102T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER;SCHEDULE-AGENT=SERVER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+"""
+
+    attendee3_data = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+RECURRENCE-ID:20140102T100000Z
+DTSTART:20140102T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+"""
+
+
+    @inlineCallbacks
+    def test_doImplicitScheduling(self):
+        """
+        Test that doImplicitScheduling fixes an inconsistent schedule-agent state when an
+        attendee stores their data.
+        """
+
+        cobj = yield self.calendarObjectUnderTest(home="user02", name="attendee2.ics")
+        yield cobj.setComponent(Component.fromString(self.attendee2_update_data))
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(home="user02", name="attendee2.ics")
+        comp = yield cobj.component()
+        self.assertTrue(comp.masterComponent() is None)
+        self.assertTrue(comp.getOrganizerScheduleAgent())
+
+        inbox = yield self.calendarUnderTest(home="user01", name="inbox")
+        cobjs = yield inbox.calendarObjects()
+        self.assertTrue(len(cobjs) == 1)
+
+
+
+class MissingOrganizerFix(ScheduleAgentFixBase):
+    """
+    Test that an attendee with a copy of an event without any organizer or attendee
+    properties is corrected when the organizer updates.
+    """
+
+    organizer_data = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+"""
+
+    organizer_update_data = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+"""
+
+    attendee2_data = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+END:VEVENT
+END:VCALENDAR
+"""
+
+    attendee3_data = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+"""
+
+
+    @inlineCallbacks
+    def test_doImplicitScheduling(self):
+        """
+        Test that doImplicitScheduling fixes an inconsistent schedule-agent state when an
+        attendee stores their data.
+        """
+
+        cobj = yield self.calendarObjectUnderTest(home="user02", name="attendee2.ics")
+        comp = yield cobj.component()
+        self.assertTrue(comp.getOrganizer() is None)
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(home="user01", name="organizer.ics")
+        yield cobj.setComponent(Component.fromString(self.organizer_update_data))
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(home="user02", name="attendee2.ics")
+        comp = yield cobj.component()
+        self.assertTrue(comp.getOrganizer() is not None)
+
+        inbox = yield self.calendarUnderTest(home="user02", name="inbox")
+        cobjs = yield inbox.calendarObjects()
+        self.assertTrue(len(cobjs) == 1)

Deleted: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py	2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py	2014-06-02 02:47:48 UTC (rev 13591)
@@ -1,232 +0,0 @@
-##
-# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet.defer import inlineCallbacks, succeed
-from twisted.trial import unittest
-
-from twistedcaldav import memcacher
-from twistedcaldav.ical import Component
-from twistedcaldav.stdconfig import config
-
-from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
-from txdav.caldav.datastore.scheduling.cuaddress import LocalCalendarUser
-
-
-class FakeImplicitProcessor(ImplicitProcessor):
-    """
-    A fake ImplicitProcessor that tracks batch refreshes.
-    """
-
-    def __init__(self):
-        self.batches = 0
-
-
-    def _enqueueBatchRefresh(self):
-        self.batches += 1
-
-
-    def writeCalendarResource(self, collection, resource, calendar):
-        return succeed(FakeResource())
-
-
-
-class FakePrincipal(object):
-
-    def __init__(self, cuaddr):
-        self.cuaddr = cuaddr
-
-
-    def calendarUserAddresses(self):
-        return (self.cuaddr,)
-
-
-
-class FakeResource(object):
-
-    def parentCollection(self):
-        return self
-
-
-    def ownerHome(self):
-        return self
-
-
-    def uid(self):
-        return None
-
-
-    def id(self):
-        return None
-
-
-
-class BatchRefresh (unittest.TestCase):
-    """
-    iCalendar support tests
-    """
-
-    def setUp(self):
-        super(BatchRefresh, self).setUp()
-        config.Memcached.Pools.Default.ClientEnabled = False
-        config.Memcached.Pools.Default.ServerEnabled = False
-        memcacher.Memcacher.allowTestCache = True
-        memcacher.Memcacher.memoryCacheInstance = None
-
-
-    @inlineCallbacks
-    def test_queueAttendeeUpdate_no_refresh(self):
-
-        self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", 5)
-
-        calendar = Component.fromString("""BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE:urn:uuid:user01
-ATTENDEE:urn:uuid:user02
-END:VEVENT
-END:VCALENDAR
-""")
-        processor = FakeImplicitProcessor()
-        processor.txn = ""
-        processor.uid = "12345-67890"
-        processor.recipient_calendar = calendar
-        yield processor.queueAttendeeUpdate(("urn:uuid:user02", "urn:uuid:user01",))
-        self.assertEqual(processor.batches, 0)
-
-
-    @inlineCallbacks
-    def test_queueAttendeeUpdate_with_refresh(self):
-
-        self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", 5)
-
-        calendar = Component.fromString("""BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE:urn:uuid:user01
-ATTENDEE:urn:uuid:user02
-ATTENDEE:urn:uuid:user03
-END:VEVENT
-END:VCALENDAR
-""")
-        processor = FakeImplicitProcessor()
-        processor.txn = ""
-        processor.uid = "12345-67890"
-        processor.recipient_calendar = calendar
-        yield processor.queueAttendeeUpdate(("urn:uuid:user02", "urn:uuid:user01",))
-        self.assertEqual(processor.batches, 1)
-
-
-    @inlineCallbacks
-    def test_queueAttendeeUpdate_count_suppressed(self):
-
-        self.patch(config.Scheduling.Options, "AttendeeRefreshCountLimit", 5)
-        self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", 5)
-
-        calendar_small = Component.fromString("""BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE:urn:uuid:user01
-ATTENDEE:urn:uuid:user02
-ATTENDEE:urn:uuid:user03
-ATTENDEE:urn:uuid:user04
-END:VEVENT
-END:VCALENDAR
-""")
-        itip_small = Component.fromString("""BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-METHOD:REPLY
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE;PARTSTAT="ACCEPTED":urn:uuid:user02
-END:VEVENT
-END:VCALENDAR
-""")
-        calendar_large = Component.fromString("""BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE:urn:uuid:user01
-ATTENDEE:urn:uuid:user02
-ATTENDEE:urn:uuid:user03
-ATTENDEE:urn:uuid:user04
-ATTENDEE:urn:uuid:user05
-ATTENDEE:urn:uuid:user06
-ATTENDEE:urn:uuid:user07
-ATTENDEE:urn:uuid:user08
-ATTENDEE:urn:uuid:user09
-ATTENDEE:urn:uuid:user10
-END:VEVENT
-END:VCALENDAR
-""")
-        itip_large = Component.fromString("""BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-METHOD:REPLY
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE;PARTSTAT="ACCEPTED":urn:uuid:user02
-END:VEVENT
-END:VCALENDAR
-""")
-
-        for count, calendar, itip, result, msg in (
-            (5, calendar_small, itip_small, 1, "Small, count=5"),
-            (5, calendar_large, itip_large, 0, "Large, count=5"),
-            (0, calendar_small, itip_small, 1, "Small, count=0"),
-            (0, calendar_large, itip_large, 1, "Large, count=0"),
-        ):
-            config.Scheduling.Options.AttendeeRefreshCountLimit = count
-            processor = FakeImplicitProcessor()
-            processor.txn = ""
-            processor.recipient_calendar = calendar.duplicate()
-            processor.uid = processor.recipient_calendar.newUID()
-            processor.recipient_calendar_resource = None
-            processor.message = itip.duplicate()
-            processor.message.newUID(processor.uid)
-            processor.originator = LocalCalendarUser(None, None)
-            processor.recipient = LocalCalendarUser(None, None)
-            processor.uid = calendar.resourceUID()
-            processor.noAttendeeRefresh = False
-
-            processed = yield processor.doImplicitOrganizerUpdate()
-            self.assertTrue(processed[3] is not None, msg=msg)
-            self.assertEqual(processor.batches, result, msg=msg)

Copied: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_processing.py (from rev 13299, CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py)
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_processing.py	                        (rev 0)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_processing.py	2014-06-02 02:47:48 UTC (rev 13591)
@@ -0,0 +1,232 @@
+##
+# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks, succeed
+from twisted.trial import unittest
+
+from twistedcaldav import memcacher
+from twistedcaldav.ical import Component
+from twistedcaldav.stdconfig import config
+
+from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
+from txdav.caldav.datastore.scheduling.cuaddress import LocalCalendarUser
+
+
+class FakeImplicitProcessor(ImplicitProcessor):
+    """
+    A fake ImplicitProcessor that tracks batch refreshes.
+    """
+
+    def __init__(self):
+        self.batches = 0
+
+
+    def _enqueueBatchRefresh(self):
+        self.batches += 1
+
+
+    def writeCalendarResource(self, collection, resource, calendar):
+        return succeed(FakeResource())
+
+
+
+class FakePrincipal(object):
+
+    def __init__(self, cuaddr):
+        self.cuaddr = cuaddr
+
+
+    def calendarUserAddresses(self):
+        return (self.cuaddr,)
+
+
+
+class FakeResource(object):
+
+    def parentCollection(self):
+        return self
+
+
+    def ownerHome(self):
+        return self
+
+
+    def uid(self):
+        return None
+
+
+    def id(self):
+        return None
+
+
+
+class BatchRefresh (unittest.TestCase):
+    """
+    iCalendar support tests
+    """
+
+    def setUp(self):
+        super(BatchRefresh, self).setUp()
+        config.Memcached.Pools.Default.ClientEnabled = False
+        config.Memcached.Pools.Default.ServerEnabled = False
+        memcacher.Memcacher.allowTestCache = True
+        memcacher.Memcacher.memoryCacheInstance = None
+
+
+    @inlineCallbacks
+    def test_queueAttendeeUpdate_no_refresh(self):
+
+        self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", 5)
+
+        calendar = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+END:VEVENT
+END:VCALENDAR
+""")
+        processor = FakeImplicitProcessor()
+        processor.txn = ""
+        processor.uid = "12345-67890"
+        processor.recipient_calendar = calendar
+        yield processor.queueAttendeeUpdate(("urn:uuid:user02", "urn:uuid:user01",))
+        self.assertEqual(processor.batches, 0)
+
+
+    @inlineCallbacks
+    def test_queueAttendeeUpdate_with_refresh(self):
+
+        self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", 5)
+
+        calendar = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+""")
+        processor = FakeImplicitProcessor()
+        processor.txn = ""
+        processor.uid = "12345-67890"
+        processor.recipient_calendar = calendar
+        yield processor.queueAttendeeUpdate(("urn:uuid:user02", "urn:uuid:user01",))
+        self.assertEqual(processor.batches, 1)
+
+
+    @inlineCallbacks
+    def test_queueAttendeeUpdate_count_suppressed(self):
+
+        self.patch(config.Scheduling.Options, "AttendeeRefreshCountLimit", 5)
+        self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", 5)
+
+        calendar_small = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+ATTENDEE:urn:uuid:user04
+END:VEVENT
+END:VCALENDAR
+""")
+        itip_small = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE;PARTSTAT="ACCEPTED":urn:uuid:user02
+END:VEVENT
+END:VCALENDAR
+""")
+        calendar_large = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+ATTENDEE:urn:uuid:user04
+ATTENDEE:urn:uuid:user05
+ATTENDEE:urn:uuid:user06
+ATTENDEE:urn:uuid:user07
+ATTENDEE:urn:uuid:user08
+ATTENDEE:urn:uuid:user09
+ATTENDEE:urn:uuid:user10
+END:VEVENT
+END:VCALENDAR
+""")
+        itip_large = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE;PARTSTAT="ACCEPTED":urn:uuid:user02
+END:VEVENT
+END:VCALENDAR
+""")
+
+        for count, calendar, itip, result, msg in (
+            (5, calendar_small, itip_small, 1, "Small, count=5"),
+            (5, calendar_large, itip_large, 0, "Large, count=5"),
+            (0, calendar_small, itip_small, 1, "Small, count=0"),
+            (0, calendar_large, itip_large, 1, "Large, count=0"),
+        ):
+            config.Scheduling.Options.AttendeeRefreshCountLimit = count
+            processor = FakeImplicitProcessor()
+            processor.txn = ""
+            processor.recipient_calendar = calendar.duplicate()
+            processor.uid = processor.recipient_calendar.newUID()
+            processor.recipient_calendar_resource = None
+            processor.message = itip.duplicate()
+            processor.message.newUID(processor.uid)
+            processor.originator = LocalCalendarUser(None, None)
+            processor.recipient = LocalCalendarUser(None, None)
+            processor.uid = calendar.resourceUID()
+            processor.noAttendeeRefresh = False
+
+            processed = yield processor.doImplicitOrganizerUpdate()
+            self.assertTrue(processed[3] is not None, msg=msg)
+            self.assertEqual(processor.batches, result, msg=msg)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140601/f4d48f00/attachment-0001.html>


More information about the calendarserver-changes mailing list