[CalendarServer-changes] [9226] CalendarServer/trunk/calendarserver/tools
source_changes at macosforge.org
source_changes at macosforge.org
Thu May 3 20:11:07 PDT 2012
Revision: 9226
http://trac.macosforge.org/projects/calendarserver/changeset/9226
Author: cdaboo at apple.com
Date: 2012-05-03 20:11:05 -0700 (Thu, 03 May 2012)
Log Message:
-----------
Work around false positive missing events. Implement mismatch fix and set of tests.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tools/calverify.py
CalendarServer/trunk/calendarserver/tools/test/test_calverify.py
Modified: CalendarServer/trunk/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/calverify.py 2012-05-02 23:29:53 UTC (rev 9225)
+++ CalendarServer/trunk/calendarserver/tools/calverify.py 2012-05-04 03:11:05 UTC (rev 9226)
@@ -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/trunk/calendarserver/tools/test/test_calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_calverify.py 2012-05-02 23:29:53 UTC (rev 9225)
+++ CalendarServer/trunk/calendarserver/tools/test/test_calverify.py 2012-05-04 03:11:05 UTC (rev 9226)
@@ -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)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120503/549f22a1/attachment-0001.html>
More information about the calendarserver-changes
mailing list