[CalendarServer-changes] [9231] CalendarServer/branches/users/gaya/sharedabgroups
source_changes at macosforge.org
source_changes at macosforge.org
Mon May 7 11:12:04 PDT 2012
Revision: 9231
http://trac.macosforge.org/projects/calendarserver/changeset/9231
Author: gaya at apple.com
Date: 2012-05-07 11:12:03 -0700 (Mon, 07 May 2012)
Log Message:
-----------
update to trunk
Modified Paths:
--------------
CalendarServer/branches/users/gaya/sharedabgroups/calendarserver/tools/calverify.py
CalendarServer/branches/users/gaya/sharedabgroups/calendarserver/tools/test/test_calverify.py
CalendarServer/branches/users/gaya/sharedabgroups/txdav/caldav/datastore/test/common.py
CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql.py
CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/test/util.py
Added Paths:
-----------
CalendarServer/branches/users/gaya/sharedabgroups/contrib/tools/harpoon.py
Modified: CalendarServer/branches/users/gaya/sharedabgroups/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/calendarserver/tools/calverify.py 2012-05-04 21:53:03 UTC (rev 9230)
+++ CalendarServer/branches/users/gaya/sharedabgroups/calendarserver/tools/calverify.py 2012-05-07 18:12:03 UTC (rev 9231)
@@ -52,11 +52,14 @@
from twisted.python import log
from twisted.python.text import wordWrap
from twisted.python.usage import Options
+from twistedcaldav import caldavxml
from twistedcaldav.dateops import pyCalendarTodatetime
from twistedcaldav.ical import Component, ignoredComponents,\
- InvalidICalendarDataError
+ InvalidICalendarDataError, Property
+from twistedcaldav.scheduling.itip import iTipGenerator
from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
from twistedcaldav.util import normalizationLookup
+from txdav.base.propertystore.base import PropertyName
from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
from txdav.common.icommondatastore import InternalDataStoreError
import base64
@@ -65,8 +68,9 @@
import sys
import time
import traceback
+import uuid
-VERSION = "2"
+VERSION = "3"
def usage(e=None):
if e:
@@ -153,6 +157,22 @@
Service which runs, exports the appropriate records, then stops the reactor.
"""
+ metadata = {
+ "accessMode": "PUBLIC",
+ "isScheduleObject": True,
+ "scheduleTag": "abc",
+ "scheduleEtags": (),
+ "hasPrivateComment": False,
+ }
+
+ metadata_inbox = {
+ "accessMode": "PUBLIC",
+ "isScheduleObject": False,
+ "scheduleTag": "",
+ "scheduleEtags": (),
+ "hasPrivateComment": False,
+ }
+
def __init__(self, store, options, output, reactor, config):
super(CalVerifyService, self).__init__()
self.store = store
@@ -167,6 +187,11 @@
self.results = {}
self.summary = []
+ self.fixAttendeesForOrganizerMissing = 0
+ self.fixAttendeesForOrganizerMismatch = 0
+ self.fixOrganizersForAttendeeMissing = 0
+ self.fixOrganizersForAttendeeMismatch = 0
+ self.fixFailed = 0
self.total = 0
self.totalErrors = None
self.totalExceptions = None
@@ -327,11 +352,11 @@
@inlineCallbacks
- def doScan(self, ical, mismatch, fix):
+ def doScan(self, ical, mismatch, fix, start=None):
self.output.write("\n---- Scanning calendar data ----\n")
- self.start = PyCalendarDateTime.getToday()
+ self.start = start if start is not None else PyCalendarDateTime.getToday()
self.start.setDateOnly(False)
self.end = self.start.duplicate()
self.end.offsetYear(1)
@@ -377,33 +402,8 @@
self.attended = []
self.attended_byuid = collections.defaultdict(list)
self.matched_attendee_to_organizer = collections.defaultdict(set)
- skipped = 0
- inboxes = 0
- for owner, resid, uid, calname, md5, organizer, created, modified in rows:
-
- # Skip owners not enabled for calendaring
- if not self.testForCalendaringUUID(owner):
- skipped += 1
- continue
-
- # Skip inboxes
- if calname == "inbox":
- inboxes += 1
- continue
-
- # If targeting a specific organizer, skip events belonging to others
- if self.options["uuid"]:
- if not organizer.startswith("urn:uuid:") or self.options["uuid"] != organizer[9:]:
- continue
+ skipped, inboxes = self.buildResourceInfo(rows)
- # Cache organizer/attendee states
- if organizer.startswith("urn:uuid:") and owner == organizer[9:]:
- self.organized.append((owner, resid, uid, md5, organizer, created, modified,))
- self.organized_byuid[uid] = (owner, resid, uid, md5, organizer, created, modified,)
- else:
- self.attended.append((owner, resid, uid, md5, organizer, created, modified,))
- self.attended_byuid[uid].append((owner, resid, uid, md5, organizer, created, modified,))
-
self.output.write("Number of organizer events to process: %s\n" % (len(self.organized),))
self.output.write("Number of attendee events to process: %s\n" % (len(self.attended,)))
self.results["Number of organizer events to process"] = len(self.organized)
@@ -423,6 +423,20 @@
self.totalErrors = 0
yield self.verifyAllAttendeesForOrganizer()
yield self.verifyAllOrganizersForAttendee()
+
+ # Need to add fix summary information
+ if fix:
+ self.addSummaryBreak()
+ self.results["Fixed missing attendee events"] = self.fixAttendeesForOrganizerMissing
+ self.results["Fixed mismatched attendee events"] = self.fixAttendeesForOrganizerMismatch
+ self.results["Fixed missing organizer events"] = self.fixOrganizersForAttendeeMissing
+ self.results["Fixed mismatched organizer events"] = self.fixOrganizersForAttendeeMismatch
+ self.results["Fix failures"] = self.fixFailed
+ self.addToSummary("Fixed missing attendee events", self.fixAttendeesForOrganizerMissing)
+ self.addToSummary("Fixed mismatched attendee events", self.fixAttendeesForOrganizerMismatch)
+ self.addToSummary("Fixed missing organizer events", self.fixOrganizersForAttendeeMissing)
+ self.addToSummary("Fixed mismatched organizer events", self.fixOrganizersForAttendeeMismatch)
+ self.addToSummary("Fix failures", self.fixFailed)
yield succeed(None)
@@ -552,6 +566,38 @@
returnValue(rows[0])
+ def buildResourceInfo(self, rows, onlyOrganizer=False, onlyAttendee=False):
+ skipped = 0
+ inboxes = 0
+ for owner, resid, uid, calname, md5, organizer, created, modified in rows:
+
+ # Skip owners not enabled for calendaring
+ if not self.testForCalendaringUUID(owner):
+ skipped += 1
+ continue
+
+ # Skip inboxes
+ if calname == "inbox":
+ inboxes += 1
+ continue
+
+ # If targeting a specific organizer, skip events belonging to others
+ if self.options["uuid"]:
+ if not organizer.startswith("urn:uuid:") or self.options["uuid"] != organizer[9:]:
+ continue
+
+ # Cache organizer/attendee states
+ if organizer.startswith("urn:uuid:") and owner == organizer[9:]:
+ if not onlyAttendee:
+ self.organized.append((owner, resid, uid, md5, organizer, created, modified,))
+ self.organized_byuid[uid] = (owner, resid, uid, md5, organizer, created, modified,)
+ else:
+ if not onlyOrganizer:
+ self.attended.append((owner, resid, uid, md5, organizer, created, modified,))
+ self.attended_byuid[uid].append((owner, resid, uid, md5, organizer, created, modified,))
+
+ return skipped, inboxes
+
def testForCalendaringUUID(self, uuid):
"""
Determine if the specified directory UUID is valid for calendaring. Keep a cache of
@@ -900,12 +946,12 @@
# Get attendee states for matching UID
eachAttendeesOwnStatus = {}
for attendeeEvent in self.attended_byuid.get(uid, ()):
- owner, attresid, uid, _ignore_md5, _ignore_organizer, att_created, att_modified = attendeeEvent
+ owner, attresid, attuid, _ignore_md5, _ignore_organizer, att_created, att_modified = attendeeEvent
calendar = yield self.getCalendar(attresid)
if calendar is None:
continue
eachAttendeesOwnStatus[owner] = self.buildAttendeeStates(calendar, self.start, self.end, attendee_only=owner)
- attendeeResIDs[(owner, uid)] = attresid
+ attendeeResIDs[(owner, attuid)] = attresid
# Look at each attendee in the organizer's meeting
for organizerAttendee, organizerViewOfStatus in organizerViewOfAttendees.iteritems():
@@ -917,6 +963,15 @@
if not self.testForCalendaringUUID(organizerAttendee):
continue
+ # Double check the missing attendee situation in case we missed it during the original query
+ if organizerAttendee not in eachAttendeesOwnStatus:
+ # Try to reload the attendee data
+ calendar, attresid = yield self.getCalendarForOwnerByUID(organizerAttendee, uid)
+ if calendar is not None:
+ eachAttendeesOwnStatus[organizerAttendee] = self.buildAttendeeStates(calendar, self.start, self.end, attendee_only=organizerAttendee)
+ attendeeResIDs[(organizerAttendee, uid)] = attresid
+ #print "Reloaded missing attendee data"
+
# If an entry for the attendee exists, then check whether attendee status matches
if organizerAttendee in eachAttendeesOwnStatus:
attendeeOwnStatus = eachAttendeesOwnStatus[organizerAttendee].get(organizerAttendee, set())
@@ -926,6 +981,7 @@
for _organizerInstance, partstat in organizerViewOfStatus.difference(attendeeOwnStatus):
if partstat not in ("DECLINED", "CANCELLED"):
results_mismatch.append((uid, resid, organizer, org_created, org_modified, organizerAttendee, att_created, att_modified))
+ self.results.setdefault("Mismatch Attendee", set()).add((uid, organizer, organizerAttendee,))
broken = True
if self.options["details"]:
self.output.write("Mismatch: on Organizer's side:\n")
@@ -939,6 +995,7 @@
if partstat not in ("CANCELLED",):
if not broken:
results_mismatch.append((uid, resid, organizer, org_created, org_modified, organizerAttendee, att_created, att_modified))
+ self.results.setdefault("Mismatch Attendee", set()).add((uid, organizer, organizerAttendee,))
broken = True
if self.options["details"]:
self.output.write("Mismatch: on Attendee's side:\n")
@@ -952,14 +1009,13 @@
for _ignore_instance_id, partstat in organizerViewOfStatus:
if partstat not in ("DECLINED", "CANCELLED"):
results_missing.append((uid, resid, organizer, organizerAttendee, org_created, org_modified))
+ self.results.setdefault("Missing Attendee", set()).add((uid, organizer, organizerAttendee,))
broken = True
break
# If there was a problem we can fix it
if broken and self.fix:
- # TODO: This is where we attempt a fix
- #self.fixEvent(organizer, organizerAttendee, eventpath, attendeePaths.get(organizerAttendee, None))
- pass
+ yield self.fixByReinvitingAttendee(resid, attendeeResIDs.get((organizerAttendee, uid)), organizerAttendee)
yield self.txn.commit()
self.txn = None
@@ -1032,7 +1088,7 @@
attended_div = 1 if attended_len < 100 else attended_len / 100
t = time.time()
- for ctr, attendeeEvent in enumerate(self.attended):
+ for ctr, attendeeEvent in enumerate(tuple(self.attended)): # self.attended might mutate during the loop
if self.options["verbose"] and divmod(ctr, attended_div)[1] == 0:
self.output.write(("\r%d of %d (%d%%) Missing: %d Mismatched: %s" % (
@@ -1067,18 +1123,32 @@
if not self.testForCalendaringUUID(organizer):
continue
+ # Double check the missing attendee situation in case we missed it during the original query
if uid not in self.organized_byuid:
+ # Try to reload the organizer info data
+ rows = yield self.getAllResourceInfoWithUID(uid)
+ self.buildResourceInfo(rows, onlyOrganizer=True)
+
+ #if uid in self.organized_byuid:
+ # print "Reloaded missing organizer data: %s" % (uid,)
+
+ if uid not in self.organized_byuid:
# Check whether attendee has all instances cancelled
if self.allCancelled(eachAttendeesOwnStatus):
continue
missing.append((uid, attendee, organizer, resid, att_created, att_modified,))
+ self.results.setdefault("Missing Organizer", set()).add((uid, attendee, organizer,))
# If there is a miss we fix by removing the attendee data
if self.fix:
- # TODO: This is where we attempt a fix
- pass
+ # This is where we attempt a fix
+ fix_result = (yield self.fixByRemovingEvent(resid))
+ if fix_result:
+ self.fixOrganizersForAttendeeMissing += 1
+ else:
+ self.fixFailed += 1
elif attendee not in self.matched_attendee_to_organizer[uid]:
# Check whether attendee has all instances cancelled
@@ -1086,11 +1156,11 @@
continue
mismatched.append((uid, attendee, organizer, resid, att_created, att_modified,))
+ self.results.setdefault("Mismatch Organizer", set()).add((uid, attendee, organizer,))
# If there is a mismatch we fix by re-inviting the attendee
if self.fix:
- # TODO: This is where we attempt a fix
- pass
+ yield self.fixByReinvitingAttendee(self.organized_byuid[uid][1], resid, attendee)
yield self.txn.commit()
self.txn = None
@@ -1153,6 +1223,124 @@
self.totalErrors += len(mismatched)
+ @inlineCallbacks
+ def fixByReinvitingAttendee(self, orgresid, attresid, attendee):
+ """
+ Fix a mismatch/missing error by having the organizer send a REQUEST for the entire event to the attendee
+ to trigger implicit scheduling to resync the attendee event.
+
+ We do not have implicit apis in the store, but really want to use store-only apis here to avoid having to create
+ "fake" HTTP requests and manipulate HTTP resources. So what we will do is emulate implicit behavior by copying the
+ organizer resource to the attendee (filtering it for the attendee's view of the event) and deposit an inbox item
+ for the same event. Right now that will wipe out any per-attendee data - notably alarms.
+ """
+
+ try:
+ cuaddr = "urn:uuid:%s" % attendee
+
+ # Get the organizer's calendar data
+ calendar = (yield self.getCalendar(orgresid))
+ calendar = Component(None, pycalendar=calendar)
+
+ # Generate an iTip message for the entire event filtered for the attendee's view
+ itipmsg = iTipGenerator.generateAttendeeRequest(calendar, (cuaddr,), None)
+
+ # Handle the case where the attendee is not actually in the organizer event at all by
+ # removing the attendee event instead of re-inviting
+ if itipmsg.resourceUID() is None:
+ yield self.fixByRemovingEvent(attresid)
+ returnValue(True)
+
+ # Convert iTip message into actual calendar data - just remove METHOD
+ attendee_calendar = itipmsg.duplicate()
+ attendee_calendar.removeProperty(attendee_calendar.getProperty("METHOD"))
+
+ # Adjust TRANSP to match PARTSTAT
+ self.setTransparencyForAttendee(attendee_calendar, cuaddr)
+
+ # Get attendee home store object
+ home = (yield self.txn.calendarHomeWithUID(attendee))
+ if home is None:
+ raise ValueError("Cannot find home")
+ inbox = (yield home.calendarWithName("inbox"))
+ if inbox is None:
+ raise ValueError("Cannot find inbox")
+
+ # Replace existing resource data, or create a new one
+ if attresid:
+ # TODO: transfer over per-attendee data - valarms
+ _ignore_homeID, calendarID = yield self.getAllResourceInfoForResourceID(attresid)
+ calendar = yield home.childWithID(calendarID)
+ calendarObj = yield calendar.objectResourceWithID(attresid)
+ calendarObj.scheduleTag = str(uuid.uuid4())
+ yield calendarObj.setComponent(attendee_calendar)
+ self.results.setdefault("Fix change event", set()).add((home.name(), calendar.name(), attendee_calendar.resourceUID(),))
+ else:
+ # Find default calendar for VEVENTs
+ defaultCalendar = (yield self.defaultCalendarForAttendee(home, inbox))
+ if defaultCalendar is None:
+ raise ValueError("Cannot find suitable default calendar")
+ yield defaultCalendar.createCalendarObjectWithName(str(uuid.uuid4()) + ".ics", attendee_calendar, self.metadata)
+ self.results.setdefault("Fix add event", set()).add((home.name(), defaultCalendar.name(), attendee_calendar.resourceUID(),))
+
+ # Write new itip message to attendee inbox
+ yield inbox.createCalendarObjectWithName(str(uuid.uuid4()) + ".ics", itipmsg, self.metadata_inbox)
+ self.results.setdefault("Fix add inbox", set()).add((home.name(), itipmsg.resourceUID(),))
+
+ yield self.txn.commit()
+ self.txn = self.store.newTransaction()
+
+ returnValue(True)
+
+ except Exception, e:
+ print "Failed to fix resource: %d for attendee: %s\n%s" % (orgresid, attendee, e,)
+ returnValue(False)
+
+
+ @inlineCallbacks
+ def defaultCalendarForAttendee(self, home, inbox):
+
+ # Check for property
+ default = inbox.properties().get(PropertyName.fromElement(caldavxml.ScheduleDefaultCalendarURL))
+ if default:
+ defaultName = str(default.children[0]).rstrip("/").split("/")[-1]
+ defaultCalendar = (yield home.calendarWithName(defaultName))
+ returnValue(defaultCalendar)
+ else:
+ # Iterate for the first calendar that supports VEVENTs
+ calendars = (yield home.calendars())
+ for calendar in calendars:
+ if calendar.name() != "inbox" and calendar.isSupportedComponent("VEVENT"):
+ returnValue(calendar)
+ else:
+ returnValue(None)
+
+
+ @inlineCallbacks
+ def fixByRemovingEvent(self, resid):
+ """
+ Remove the calendar resource specified by resid - this is a force remove - no implicit
+ scheduling is required so we use store apis directly.
+ """
+
+ try:
+ homeID, calendarID = yield self.getAllResourceInfoForResourceID(resid)
+ home = yield self.txn.calendarHomeWithResourceID(homeID)
+ calendar = yield home.childWithID(calendarID)
+ calendarObj = yield calendar.objectResourceWithID(resid)
+ objname = calendarObj.name()
+ yield calendar._removeObjectResource(calendarObj)
+ yield self.txn.commit()
+ self.txn = self.store.newTransaction()
+
+ self.results.setdefault("Fix remove", set()).add((home.name(), calendar.name(), objname,))
+
+ returnValue(True)
+ except Exception, e:
+ print "Failed to remove resource whilst fixing: %d\n%s" % (resid, e,)
+ returnValue(False)
+
+
def addToSummary(self, title, count, total=None):
if total is not None:
percent = safePercent(count, total),
@@ -1223,6 +1411,31 @@
returnValue(caldata)
+ @inlineCallbacks
+ def getCalendarForOwnerByUID(self, owner, uid):
+ co = schema.CALENDAR_OBJECT
+ cb = schema.CALENDAR_BIND
+ ch = schema.CALENDAR_HOME
+
+ kwds = { "OWNER" : owner, "UID": uid }
+ rows = (yield Select(
+ [co.ICALENDAR_TEXT, co.RESOURCE_ID,],
+ From=ch.join(
+ cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID)).join(
+ co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
+ cb.BIND_MODE == _BIND_MODE_OWN).And(
+ cb.CALENDAR_RESOURCE_NAME != "inbox")),
+ Where=(ch.OWNER_UID == Parameter("OWNER")).And(co.ICALENDAR_UID == Parameter("UID")),
+ ).on(self.txn, **kwds))
+
+ try:
+ caldata = PyCalendar.parseText(rows[0][0]) if rows else None
+ except PyCalendarError:
+ returnValue((None, None,))
+
+ returnValue((caldata, rows[0][1] if rows else None,))
+
+
def masterComponent(self, calendar):
"""
Return the master iCal component in this calendar.
@@ -1302,6 +1515,21 @@
return all_cancelled
+ def setTransparencyForAttendee(self, calendar, attendee):
+ """
+ Set the TRANSP property based on the PARTSTAT value on matching ATTENDEE properties
+ in each component.
+ """
+ for component in calendar.subcomponents():
+ if component.name() in ignoredComponents:
+ continue
+ prop = component.getAttendeeProperty(attendee)
+ addTransp = False
+ if prop:
+ partstat = prop.parameterValue("PARTSTAT", "NEEDS-ACTION")
+ addTransp = partstat in ("NEEDS-ACTION", "DECLINED",)
+ component.replaceProperty(Property("TRANSP", "TRANSPARENT" if addTransp else "OPAQUE"))
+
def directoryService(self):
"""
Get an appropriate directory service for this L{CalVerifyService}'s
Modified: CalendarServer/branches/users/gaya/sharedabgroups/calendarserver/tools/test/test_calverify.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/calendarserver/tools/test/test_calverify.py 2012-05-04 21:53:03 UTC (rev 9230)
+++ CalendarServer/branches/users/gaya/sharedabgroups/calendarserver/tools/test/test_calverify.py 2012-05-07 18:12:03 UTC (rev 9231)
@@ -21,12 +21,16 @@
from StringIO import StringIO
from calendarserver.tap.util import getRootResource
from calendarserver.tools.calverify import CalVerifyService
+from pycalendar.datetime import PyCalendarDateTime
from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.trial import unittest
+from twistedcaldav import caldavxml
from twistedcaldav.config import config
+from txdav.base.propertystore.base import PropertyName
from txdav.caldav.datastore import util
from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom, CommonCommonTests
+from txdav.xml import element as davxml
import os
@@ -333,9 +337,9 @@
""".replace("\n", "\r\n")
-class CalVerifyTests(CommonCommonTests, unittest.TestCase):
+class CalVerifyDataTests(CommonCommonTests, unittest.TestCase):
"""
- Tests for deleting events older than a given date
+ Tests calverify for iCalendar data problems.
"""
metadata = {
@@ -365,7 +369,7 @@
@inlineCallbacks
def setUp(self):
- yield super(CalVerifyTests, self).setUp()
+ yield super(CalVerifyDataTests, self).setUp()
self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
yield self.populate()
@@ -795,3 +799,687 @@
okbase64 = okbase64.replace("\r\n ", "")
self.assertEqual(calverifyNo64.fixBadOldCuaLines(bad), oknobase64)
self.assertEqual(calverify64.fixBadOldCuaLines(bad), okbase64)
+
+
+class CalVerifyMismatchTestsBase(CommonCommonTests, unittest.TestCase):
+ """
+ Tests calverify for iCalendar mismatch problems.
+ """
+
+ metadata = {
+ "accessMode": "PUBLIC",
+ "isScheduleObject": True,
+ "scheduleTag": "abc",
+ "scheduleEtags": (),
+ "hasPrivateComment": False,
+ }
+
+ uuid1 = "D46F3D71-04B7-43C2-A7B6-6F92F92E61D0"
+ uuid2 = "47B16BB4-DB5F-4BF6-85FE-A7DA54230F92"
+ uuid3 = "AC478592-7783-44D1-B2AE-52359B4E8415"
+
+ @inlineCallbacks
+ def setUp(self):
+ yield super(CalVerifyMismatchTestsBase, self).setUp()
+ self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
+ yield self.populate()
+
+ inbox = (yield self.calendarUnderTest(self.uuid3, "inbox"))
+ inbox.properties()[PropertyName.fromElement(caldavxml.ScheduleDefaultCalendarURL)] = caldavxml.ScheduleDefaultCalendarURL(
+ davxml.HRef.fromString("/calendars/__uids__/%s/calendar2/" % (self.uuid3,))
+ )
+ yield self.commit()
+
+ self.patch(config.DirectoryService.params, "xmlFile",
+ os.path.join(
+ os.path.dirname(__file__), "calverify", "accounts.xml"
+ )
+ )
+ self.patch(config.ResourceService.params, "xmlFile",
+ os.path.join(
+ os.path.dirname(__file__), "calverify", "resources.xml"
+ )
+ )
+ self.rootResource = getRootResource(config, self._sqlCalendarStore)
+ self.directory = self.rootResource.getDirectory()
+
+
+ @inlineCallbacks
+ def populate(self):
+
+ # Need to bypass normal validation inside the store
+ util.validationBypass = True
+ yield populateCalendarsFrom(self.requirements, self.storeUnderTest(), migrating=True)
+ util.validationBypass = False
+ self.notifierFactory.reset()
+
+
+ def storeUnderTest(self):
+ """
+ Create and return a L{CalendarStore} for testing.
+ """
+ return self._sqlCalendarStore
+
+
+ @inlineCallbacks
+ def homeUnderTest(self, name=None, txn=None):
+ """
+ Get the calendar home detailed by C{requirements[name]}.
+ """
+ if txn is None:
+ txn = self.transactionUnderTest()
+ returnValue((yield txn.calendarHomeWithUID(name)))
+
+
+ @inlineCallbacks
+ def calendarUnderTest(self, home_name, name="calendar", txn=None):
+ """
+ Get the calendar detailed by C{requirements[home_name][name]}.
+ """
+ returnValue((yield
+ (yield self.homeUnderTest(home_name, txn)).calendarWithName(name))
+ )
+
+
+ @inlineCallbacks
+ def calendarObjectUnderTest(self, home_name, calendar_name, name, txn=None):
+ """
+ Get the calendar object detailed by C{requirements[home_name][calendar_name][name]}.
+ """
+ returnValue((yield
+ (yield self.calendarUnderTest(home_name, calendar_name, txn)).calendarObjectWithName(name))
+ )
+
+
+class CalVerifyMismatchTestsNonRecurring(CalVerifyMismatchTestsBase):
+ """
+ Tests calverify for iCalendar mismatch problems for non-recurring events.
+ """
+
+ # Organizer has event, attendees do not
+ MISSING_ATTENDEE_1_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISSING_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ # Attendees have event, organizer does not
+ MISSING_ORGANIZER_2_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISSING_ORGANIZER_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISSING_ORGANIZER_3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISSING_ORGANIZER_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ # Attendee partstat mismatch
+ MISMATCH_ATTENDEE_1_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=DECLINED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH_ATTENDEE_2_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH_ATTENDEE_3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ # Attendee events outside time range
+ MISMATCH2_ATTENDEE_1_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH2_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=DECLINED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH2_ATTENDEE_2_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH2_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:19990307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH2_ATTENDEE_3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH2_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:19990307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ # Organizer event outside time range
+ MISMATCH_ORGANIZER_1_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH_ORGANIZER_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:19990307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=DECLINED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH_ORGANIZER_2_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH_ORGANIZER_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ # Attendee uuid3 has event with different organizer
+ MISMATCH3_ATTENDEE_1_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH3_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH3_ATTENDEE_2_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH3_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH3_ATTENDEE_3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH3_ATTENDEE_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH_ORGANIZER_3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH_ORGANIZER_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ # Attendee uuid3 has event they are not invited to
+ MISMATCH2_ORGANIZER_1_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH2_ORGANIZER_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=DECLINED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH2_ORGANIZER_2_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH2_ORGANIZER_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=DECLINED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+ MISMATCH2_ORGANIZER_3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:MISMATCH2_ORGANIZER_ICS
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE;PARTSTAT=DECLINED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
+ requirements = {
+ CalVerifyMismatchTestsBase.uuid1 : {
+ "calendar" : {
+ "missing_attendee.ics" : (MISSING_ATTENDEE_1_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched_attendee.ics" : (MISMATCH_ATTENDEE_1_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched2_attendee.ics" : (MISMATCH2_ATTENDEE_1_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched3_attendee.ics" : (MISMATCH3_ATTENDEE_1_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched_organizer.ics" : (MISMATCH_ORGANIZER_1_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched2_organizer.ics" : (MISMATCH2_ORGANIZER_1_ICS, CalVerifyMismatchTestsBase.metadata,),
+ },
+ "inbox" : {},
+ },
+ CalVerifyMismatchTestsBase.uuid2 : {
+ "calendar" : {
+ "mismatched_attendee.ics" : (MISMATCH_ATTENDEE_2_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched2_attendee.ics" : (MISMATCH2_ATTENDEE_2_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched3_attendee.ics" : (MISMATCH3_ATTENDEE_2_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "missing_organizer.ics" : (MISSING_ORGANIZER_2_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched_organizer.ics" : (MISMATCH_ORGANIZER_2_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched2_organizer.ics" : (MISMATCH2_ORGANIZER_2_ICS, CalVerifyMismatchTestsBase.metadata,),
+ },
+ "inbox" : {},
+ },
+ CalVerifyMismatchTestsBase.uuid3 : {
+ "calendar" : {
+ "mismatched_attendee.ics" : (MISMATCH_ATTENDEE_3_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched3_attendee.ics" : (MISMATCH3_ATTENDEE_3_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "missing_organizer.ics" : (MISSING_ORGANIZER_3_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched2_organizer.ics" : (MISMATCH2_ORGANIZER_3_ICS, CalVerifyMismatchTestsBase.metadata,),
+ },
+ "calendar2" : {
+ "mismatched_organizer.ics" : (MISMATCH_ORGANIZER_3_ICS, CalVerifyMismatchTestsBase.metadata,),
+ "mismatched2_attendee.ics" : (MISMATCH2_ATTENDEE_3_ICS, CalVerifyMismatchTestsBase.metadata,),
+ },
+ "inbox" : {},
+ },
+ }
+
+ @inlineCallbacks
+ def test_scanMismatchOnly(self):
+ """
+ CalVerifyService.doScan without fix for mismatches. Make sure it detects
+ as much as it can. Make sure sync-token is not changed.
+ """
+
+ sync_token_old1 = (yield (yield self.calendarUnderTest(self.uuid1)).syncToken())
+ sync_token_old2 = (yield (yield self.calendarUnderTest(self.uuid2)).syncToken())
+ sync_token_old3 = (yield (yield self.calendarUnderTest(self.uuid3)).syncToken())
+ self.commit()
+
+ options = {
+ "ical":False,
+ "badcua":False,
+ "mismatch":True,
+ "nobase64":False,
+ "verbose":False,
+ "details":False,
+ "uid":"",
+ "uuid":"",
+ }
+ output = StringIO()
+ calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+ yield calverify.doScan(False, True, False, start=PyCalendarDateTime(2000, 1, 1, 0, 0, 0))
+
+ self.assertEqual(calverify.results["Number of events to process"], 15)
+ self.assertEqual(calverify.results["Missing Attendee"], set((
+ ("MISSING_ATTENDEE_ICS", self.uuid1, self.uuid2,),
+ ("MISSING_ATTENDEE_ICS", self.uuid1, self.uuid3,),
+ )))
+ self.assertEqual(calverify.results["Mismatch Attendee"], set((
+ ("MISMATCH_ATTENDEE_ICS", self.uuid1, self.uuid2,),
+ ("MISMATCH_ATTENDEE_ICS", self.uuid1, self.uuid3,),
+ ("MISMATCH2_ATTENDEE_ICS", self.uuid1, self.uuid2,),
+ ("MISMATCH2_ATTENDEE_ICS", self.uuid1, self.uuid3,),
+ ("MISMATCH3_ATTENDEE_ICS", self.uuid1, self.uuid3,),
+ )))
+ self.assertEqual(calverify.results["Missing Organizer"], set((
+ ("MISSING_ORGANIZER_ICS", self.uuid2, self.uuid1,),
+ ("MISSING_ORGANIZER_ICS", self.uuid3, self.uuid1,),
+ )))
+ self.assertEqual(calverify.results["Mismatch Organizer"], set((
+ ("MISMATCH_ORGANIZER_ICS", self.uuid2, self.uuid1,),
+ ("MISMATCH_ORGANIZER_ICS", self.uuid3, self.uuid1,),
+ ("MISMATCH2_ORGANIZER_ICS", self.uuid3, self.uuid1,),
+ )))
+
+ self.assertTrue("Fix change event" not in calverify.results)
+ self.assertTrue("Fix add event" not in calverify.results)
+ self.assertTrue("Fix add inbox" not in calverify.results)
+ self.assertTrue("Fix remove" not in calverify.results)
+
+ sync_token_new1 = (yield (yield self.calendarUnderTest(self.uuid1)).syncToken())
+ sync_token_new2 = (yield (yield self.calendarUnderTest(self.uuid2)).syncToken())
+ sync_token_new3 = (yield (yield self.calendarUnderTest(self.uuid3)).syncToken())
+ self.assertEqual(sync_token_old1, sync_token_new1)
+ self.assertEqual(sync_token_old2, sync_token_new2)
+ self.assertEqual(sync_token_old3, sync_token_new3)
+
+
+ @inlineCallbacks
+ def test_fixMismatch(self):
+ """
+ CalVerifyService.doScan with fix for mismatches. Make sure it detects
+ and fixes as much as it can. Make sure sync-token is not changed.
+ """
+
+ sync_token_old1 = (yield (yield self.calendarUnderTest(self.uuid1)).syncToken())
+ sync_token_old2 = (yield (yield self.calendarUnderTest(self.uuid2)).syncToken())
+ sync_token_old3 = (yield (yield self.calendarUnderTest(self.uuid3)).syncToken())
+ self.commit()
+
+ options = {
+ "ical":False,
+ "badcua":False,
+ "mismatch":True,
+ "nobase64":False,
+ "verbose":False,
+ "details":False,
+ "uid":"",
+ "uuid":"",
+ }
+ output = StringIO()
+ calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+ yield calverify.doScan(False, True, True, start=PyCalendarDateTime(2000, 1, 1, 0, 0, 0))
+
+ self.assertEqual(calverify.results["Number of events to process"], 15)
+ self.assertEqual(calverify.results["Missing Attendee"], set((
+ ("MISSING_ATTENDEE_ICS", self.uuid1, self.uuid2,),
+ ("MISSING_ATTENDEE_ICS", self.uuid1, self.uuid3,),
+ )))
+ self.assertEqual(calverify.results["Mismatch Attendee"], set((
+ ("MISMATCH_ATTENDEE_ICS", self.uuid1, self.uuid2,),
+ ("MISMATCH_ATTENDEE_ICS", self.uuid1, self.uuid3,),
+ ("MISMATCH2_ATTENDEE_ICS", self.uuid1, self.uuid2,),
+ ("MISMATCH2_ATTENDEE_ICS", self.uuid1, self.uuid3,),
+ ("MISMATCH3_ATTENDEE_ICS", self.uuid1, self.uuid3,),
+ )))
+ self.assertEqual(calverify.results["Missing Organizer"], set((
+ ("MISSING_ORGANIZER_ICS", self.uuid2, self.uuid1,),
+ ("MISSING_ORGANIZER_ICS", self.uuid3, self.uuid1,),
+ )))
+ self.assertEqual(calverify.results["Mismatch Organizer"], set((
+ ("MISMATCH_ORGANIZER_ICS", self.uuid2, self.uuid1,),
+ ("MISMATCH_ORGANIZER_ICS", self.uuid3, self.uuid1,),
+ ("MISMATCH2_ORGANIZER_ICS", self.uuid3, self.uuid1,),
+ )))
+
+ self.assertEqual(calverify.results["Fix change event"], set((
+ (self.uuid2, "calendar", "MISMATCH_ATTENDEE_ICS",),
+ (self.uuid3, "calendar", "MISMATCH_ATTENDEE_ICS",),
+ (self.uuid2, "calendar", "MISMATCH2_ATTENDEE_ICS",),
+ (self.uuid3, "calendar2", "MISMATCH2_ATTENDEE_ICS",),
+ (self.uuid3, "calendar", "MISMATCH3_ATTENDEE_ICS",),
+ (self.uuid2, "calendar", "MISMATCH_ORGANIZER_ICS",),
+ (self.uuid3, "calendar2", "MISMATCH_ORGANIZER_ICS",),
+ )))
+
+ self.assertEqual(calverify.results["Fix add event"], set((
+ (self.uuid2, "calendar", "MISSING_ATTENDEE_ICS",),
+ (self.uuid3, "calendar2", "MISSING_ATTENDEE_ICS",),
+ )))
+
+ self.assertEqual(calverify.results["Fix add inbox"], set((
+ (self.uuid2, "MISSING_ATTENDEE_ICS",),
+ (self.uuid3, "MISSING_ATTENDEE_ICS",),
+ (self.uuid2, "MISMATCH_ATTENDEE_ICS",),
+ (self.uuid3, "MISMATCH_ATTENDEE_ICS",),
+ (self.uuid2, "MISMATCH2_ATTENDEE_ICS",),
+ (self.uuid3, "MISMATCH2_ATTENDEE_ICS",),
+ (self.uuid3, "MISMATCH3_ATTENDEE_ICS",),
+ (self.uuid2, "MISMATCH_ORGANIZER_ICS",),
+ (self.uuid3, "MISMATCH_ORGANIZER_ICS",),
+ )))
+
+ self.assertEqual(calverify.results["Fix remove"], set((
+ (self.uuid2, "calendar", "missing_organizer.ics",),
+ (self.uuid3, "calendar", "missing_organizer.ics",),
+ (self.uuid3, "calendar", "mismatched2_organizer.ics",),
+ )))
+ obj = yield self.calendarObjectUnderTest(self.uuid2, "calendar", "missing_organizer.ics")
+ self.assertEqual(obj, None)
+ obj = yield self.calendarObjectUnderTest(self.uuid3, "calendar", "missing_organizer.ics")
+ self.assertEqual(obj, None)
+ obj = yield self.calendarObjectUnderTest(self.uuid3, "calendar", "mismatched2_organizer.ics")
+ self.assertEqual(obj, None)
+
+ sync_token_new1 = (yield (yield self.calendarUnderTest(self.uuid1)).syncToken())
+ sync_token_new2 = (yield (yield self.calendarUnderTest(self.uuid2)).syncToken())
+ sync_token_new3 = (yield (yield self.calendarUnderTest(self.uuid3)).syncToken())
+ self.assertEqual(sync_token_old1, sync_token_new1)
+ self.assertNotEqual(sync_token_old2, sync_token_new2)
+ self.assertNotEqual(sync_token_old3, sync_token_new3)
+
+ # Re-scan after changes to make sure there are no errors
+ self.commit()
+ calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+ yield calverify.doScan(False, True, False, start=PyCalendarDateTime(2000, 1, 1, 0, 0, 0))
+
+ self.assertEqual(calverify.results["Number of events to process"], 14)
+ self.assertTrue("Missing Attendee" not in calverify.results)
+ self.assertTrue("Mismatch Attendee" not in calverify.results)
+ self.assertTrue("Missing Organizer" not in calverify.results)
+ self.assertTrue("Mismatch Organizer" not in calverify.results)
+ self.assertTrue("Fix add event" not in calverify.results)
+ self.assertTrue("Fix add inbox" not in calverify.results)
+ self.assertTrue("Fix remove" not in calverify.results)
Copied: CalendarServer/branches/users/gaya/sharedabgroups/contrib/tools/harpoon.py (from rev 9230, CalendarServer/trunk/contrib/tools/harpoon.py)
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/contrib/tools/harpoon.py (rev 0)
+++ CalendarServer/branches/users/gaya/sharedabgroups/contrib/tools/harpoon.py 2012-05-07 18:12:03 UTC (rev 9231)
@@ -0,0 +1,62 @@
+#!/ngs/app/ical/code/bin/python
+
+##
+# Copyright (c) 2012 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.
+##
+
+# Sends SIGTERM to any calendar server child process whose VSIZE exceeds 2GB
+# Only for use in a specific environment
+
+import os
+import signal
+
+CUTOFFBYTES = 2 * 1024 * 1024 * 1024
+PROCDIR = "/proc"
+PYTHON = "/ngs/app/ical/code/bin/python"
+CMDARG = "LogID"
+
+serverProcessCount = 0
+numKilled = 0
+
+for pidString in sorted(os.listdir(PROCDIR)):
+
+ try:
+ pidNumber = int(pidString)
+ except ValueError:
+ # Not a process number
+ continue
+
+ pidDir = os.path.join(PROCDIR, pidString)
+ statsFile = os.path.join(pidDir, "stat")
+ statLine = open(statsFile).read()
+ stats = statLine.split()
+ vsize = int(stats[22])
+ cmdFile = os.path.join(pidDir, "cmdline")
+ if os.path.exists(cmdFile):
+ cmdLine = open(cmdFile).read().split('\x00')
+ if cmdLine[0].startswith(PYTHON):
+ for arg in cmdLine[1:]:
+ if arg.startswith(CMDARG):
+ break
+ else:
+ continue
+ serverProcessCount += 1
+ if vsize > CUTOFFBYTES:
+ print "Killing process %d with VSIZE %d" % (pidNumber, vsize)
+ os.kill(pidNumber, signal.SIGTERM)
+ numKilled += 1
+
+print "Examined %d server processes" % (serverProcessCount,)
+print "Killed %d processes" % (numKilled,)
Modified: CalendarServer/branches/users/gaya/sharedabgroups/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/txdav/caldav/datastore/test/common.py 2012-05-04 21:53:03 UTC (rev 9230)
+++ CalendarServer/branches/users/gaya/sharedabgroups/txdav/caldav/datastore/test/common.py 2012-05-07 18:12:03 UTC (rev 9231)
@@ -78,13 +78,13 @@
"4.ics",
]
-
home1_calendarNames = [
"calendar_1",
"calendar_2",
"calendar_empty",
]
+OTHER_HOME_UID = "home_splits"
test_event_text = (
"BEGIN:VCALENDAR\r\n"
@@ -990,9 +990,9 @@
L{ICalendar.shareWith} will share a calendar with a given home UID.
"""
cal = yield self.calendarUnderTest()
- OTHER_HOME_UID = "home_splits"
other = yield self.homeUnderTest(name=OTHER_HOME_UID)
newCalName = yield cal.shareWith(other, _BIND_MODE_WRITE)
+ self.sharedName = newCalName
yield self.commit()
normalCal = yield self.calendarUnderTest()
otherHome = yield self.homeUnderTest(name=OTHER_HOME_UID)
@@ -1021,11 +1021,13 @@
"""
yield self.test_shareWith()
# yield self.commit() # txn is none? why?
- OTHER_HOME_UID = "home_splits"
cal = yield self.calendarUnderTest()
other = yield self.homeUnderTest(name=OTHER_HOME_UID)
newName = yield cal.shareWith(other, _BIND_MODE_READ)
- otherCal = yield other.sharedChildWithName(newName)
+ otherCal = yield other.sharedChildWithName(self.sharedName)
+
+ # Name should not change just because we updated the mode.
+ self.assertEqual(newName, self.sharedName)
self.assertNotIdentical(otherCal, None)
# FIXME: permission information should be visible on the retrieved
@@ -1036,6 +1038,38 @@
@inlineCallbacks
+ def test_unshareWith(self, commit=False):
+ """
+ L{ICalendar.unshareWith} will remove a previously-shared calendar from
+ another user's calendar home.
+ """
+ # XXX: ideally this would actually be using the shared calendar object
+ # from the shareee's home and just calling .unshare() on it.
+ yield self.test_shareWith()
+ if commit:
+ yield self.commit()
+ cal = yield self.calendarUnderTest()
+ other = yield self.homeUnderTest(name=OTHER_HOME_UID)
+ newName = yield cal.unshareWith(other)
+ otherCal = yield other.sharedChildWithName(newName)
+ self.assertIdentical(otherCal, None)
+ invites = yield cal.retrieveOldInvites().allRecords()
+ self.assertEqual(len(invites), 0)
+ shares = yield other.retrieveOldShares().allRecords()
+ self.assertEqual(len(shares), 0)
+
+
+ @inlineCallbacks
+ def test_unshareWithInDifferentTransaction(self):
+ """
+ L{ICalendar.unshareWith} will remove a previously-shared calendar from
+ another user's calendar home, assuming the sharing was committed in a
+ previous transaction.
+ """
+ yield self.test_unshareWith(True)
+
+
+ @inlineCallbacks
def test_hasCalendarResourceUIDSomewhereElse(self):
"""
L{ICalendarHome.hasCalendarResourceUIDSomewhereElse} will determine if
Modified: CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql.py 2012-05-04 21:53:03 UTC (rev 9230)
+++ CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql.py 2012-05-07 18:12:03 UTC (rev 9231)
@@ -2028,6 +2028,31 @@
returnValue(sharedName)
+ @inlineCallbacks
+ def unshareWith(self, shareeHome):
+ """
+ Remove the shared version of this (owned) L{CommonHomeChild} from the
+ referenced L{CommonHome}.
+
+ @see: L{CommonHomeChild.shareWith}
+
+ @param shareeHome: The home with which this L{CommonHomeChild} was
+ previously shared.
+
+ @return: a L{Deferred} which will fire with the previously-used name.
+ """
+ bind = self._bindSchema
+ resourceName = (yield Delete(
+ From=bind,
+ Where=(bind.RESOURCE_ID == Parameter("resourceID"))
+ .And(bind.HOME_RESOURCE_ID == Parameter("homeID")),
+ Return=bind.RESOURCE_NAME,
+ ).on(self._txn, resourceID=self._resourceID,
+ homeID=shareeHome._resourceID))[0][0]
+ shareeHome._sharedChildren.pop(resourceName, None)
+ returnValue(resourceName)
+
+
@classmethod
@inlineCallbacks
def loadAllObjects(cls, home, owned):
Modified: CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/test/util.py 2012-05-04 21:53:03 UTC (rev 9230)
+++ CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/test/util.py 2012-05-07 18:12:03 UTC (rev 9231)
@@ -504,7 +504,7 @@
def transactionUnderTest(self):
"""
Create a transaction from C{storeUnderTest} and save it as
- C[lastTransaction}. Also makes sure to use the same store, saving the
+ C{lastTransaction}. Also makes sure to use the same store, saving the
value from C{storeUnderTest}.
"""
if self.lastTransaction is None:
@@ -545,7 +545,7 @@
def abort(self):
"""
- Abort the last transaction created from C[transactionUnderTest}, and
+ Abort the last transaction created from C{transactionUnderTest}, and
clear it.
"""
result = self.lastTransaction.abort()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120507/b0705ee5/attachment-0001.html>
More information about the calendarserver-changes
mailing list