[CalendarServer-changes] [9039] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu Apr 12 09:57:55 PDT 2012


Revision: 9039
          http://trac.macosforge.org/projects/calendarserver/changeset/9039
Author:   cdaboo at apple.com
Date:     2012-04-12 09:57:54 -0700 (Thu, 12 Apr 2012)
Log Message:
-----------
Calverify now set to detect and fix invalid http/https cuaddresses. Added tests and changes to support testing.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/calverify.py
    CalendarServer/trunk/txdav/caldav/datastore/util.py
    CalendarServer/trunk/txdav/common/datastore/test/util.py

Added Paths:
-----------
    CalendarServer/trunk/calendarserver/tools/test/calverify/
    CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml
    CalendarServer/trunk/calendarserver/tools/test/calverify/resources.xml
    CalendarServer/trunk/calendarserver/tools/test/test_calverify.py

Modified: CalendarServer/trunk/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/calverify.py	2012-04-12 08:11:09 UTC (rev 9038)
+++ CalendarServer/trunk/calendarserver/tools/calverify.py	2012-04-12 16:57:54 UTC (rev 9039)
@@ -38,9 +38,9 @@
 
 """
 
-from calendarserver.tap.util import directoryFromConfig
 from calendarserver.tools import tables
 from calendarserver.tools.cmdline import utilityMain
+from calendarserver.tools.util import getDirectory
 from pycalendar import definitions
 from pycalendar.calendar import PyCalendar
 from pycalendar.datetime import PyCalendarDateTime
@@ -56,6 +56,7 @@
 from twistedcaldav.ical import Component, ignoredComponents,\
     InvalidICalendarDataError
 from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+from twistedcaldav.util import normalizationLookup
 from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
 from txdav.common.icommondatastore import InternalDataStoreError
 import collections
@@ -148,6 +149,10 @@
         self.reactor = reactor
         self.config = config
         self._directory = None
+        
+        self.cuaCache = {}
+        
+        self.results = {}
 
 
     def startService(self):
@@ -182,14 +187,14 @@
         """
         Report on home collections for which there are no directory records. 
         """
-        print "\n---- Finding calendar homes with no directory record ----"
+        self.output.write("\n---- Finding calendar homes with no directory record ----\n")
         self.txn = self.store.newTransaction()
 
         if self.options["verbose"]:
             t = time.time()
         uids = yield self.getAllHomeUIDs()
         if self.options["verbose"]:
-            print "getAllHomeUIDs time: %.1fs" % (time.time() - t,)
+            self.output.write("getAllHomeUIDs time: %.1fs\n" % (time.time() - t,))
         missing = []
         wrong_server = []
         uids_len = len(uids)
@@ -197,11 +202,11 @@
 
         for ctr, uid in enumerate(uids):
             if self.options["verbose"] and divmod(ctr, uids_div)[1] == 0:
-                print "%d of %d (%d%%)" % (
+                self.output.write("%d of %d (%d%%)\n" % (
                     ctr+1,
                     uids_len,
                     ((ctr+1) * 100 / uids_len),
-                )
+                ))
 
             record = self.directoryService().recordWithGUID(uid)
             if record is None:
@@ -276,7 +281,7 @@
     @inlineCallbacks
     def doScan(self, ical, mismatch, fix):
         
-        print "\n---- Scanning calendar data ----"
+        self.output.write("\n---- Scanning calendar data ----\n")
 
         self.start = PyCalendarDateTime.getToday()
         self.start.setDateOnly(False)
@@ -308,8 +313,9 @@
         self.txn = None
 
         if self.options["verbose"]:
-            print "%s time: %.1fs" % (descriptor, time.time() - t,)
-        print "Number of events to process: %s" % (len(rows,))
+            self.output.write("%s time: %.1fs\n" % (descriptor, time.time() - t,))
+        self.output.write("Number of events to process: %s\n" % (len(rows,)))
+        self.results["Number of events to process"] = len(rows)
         
         # Split into organizer events and attendee events
         self.organized = []
@@ -325,8 +331,10 @@
                 self.attended.append((owner, resid, uid, md5, organizer, created, modified,))
                 self.attended_byuid[uid].append((owner, resid, uid, md5, organizer, created, modified,))
                 
-        print "Number of organizer events to process: %s" % (len(self.organized),)
-        print "Number of attendee events to process: %s" % (len(self.attended,))
+        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)
+        self.results["Number of attendee events to process"] = len(self.attended)
 
         if ical:
             yield self.calendarDataCheck(rows)
@@ -448,7 +456,7 @@
         Check each calendar resource for valid iCalendar data.
         """
 
-        print "\n---- Verifying each calendar object resource ----"
+        self.output.write("\n---- Verifying each calendar object resource ----\n")
         self.txn = self.store.newTransaction()
 
         if self.options["verbose"]:
@@ -466,9 +474,9 @@
             count += 1
             if self.options["verbose"]:
                 if count == 1:
-                    print "Bad/Current/Total"
+                    self.output.write("Bad/Current/Total\n")
                 if divmod(count, 100)[1] == 0:
-                    print "%s/%s/%s" % (badlen, count, total,)
+                    self.output.write("%s/%s/%s\n" % (badlen, count, total,))
             
             # To avoid holding locks on all the rows scanned, commit every 100 resources
             if divmod(count, 100)[1] == 0:
@@ -494,13 +502,15 @@
         self.output.write("\n")
         self.output.write("Bad iCalendar data (total=%d):\n" % (len(results_bad),))
         table.printTable(os=self.output)
+        
+        self.results["Bad iCalendar data"] = results_bad
          
         if self.options["verbose"]:
             diff_time = time.time() - t
-            print "Time: %.2f s  Average: %.1f ms/resource" % (
+            self.output.write("Time: %.2f s  Average: %.1f ms/resource\n" % (
                 diff_time,
                 (1000.0 * diff_time) / total,
-            )
+            ))
 
     errorPrefix = "Calendar data had unfixable problems:\n  "
 
@@ -541,15 +551,33 @@
 
     def noPrincipalPathCUAddresses(self, component, doFix):
         
+        def lookupFunction(cuaddr, principalFunction, config):
+    
+            # Return cached results, if any.
+            if self.cuaCache.has_key(cuaddr):
+                return self.cuaCache[cuaddr]
+    
+            result = normalizationLookup(cuaddr, principalFunction, config)
+    
+            # Cache the result
+            self.cuaCache[cuaddr] = result
+            return result
+
         for subcomponent in component.subcomponents():
             if subcomponent.name() in ignoredComponents:
                 continue
             organizer = subcomponent.getProperty("ORGANIZER")
             if organizer and organizer.value().startswith("http"):
-                raise InvalidICalendarDataError("iCalendar ORGANIZER starts with 'http(s)'")
+                if doFix:
+                    component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
+                else:
+                    raise InvalidICalendarDataError("iCalendar ORGANIZER starts with 'http(s)'")
             for attendee in subcomponent.properties("ATTENDEE"):
                 if attendee.value().startswith("http"):
-                    raise InvalidICalendarDataError("iCalendar ATTENDEE starts with 'http(s)'")
+                    if doFix:
+                        component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
+                    else:
+                        raise InvalidICalendarDataError("iCalendar ATTENDEE starts with 'http(s)'")
 
     @inlineCallbacks
     def fixCalendarData(self, resid):
@@ -573,6 +601,7 @@
             component.validCalendarData(doFix=True, validateRecurrences=True)
             component.validCalendarForCalDAV(methodAllowed=False)
             component.validOrganizerForScheduling(doFix=True)
+            self.noPrincipalPathCUAddresses(component, doFix=True)
         except ValueError:
             result = False
             message = "Failed fix: "
@@ -594,7 +623,7 @@
         view of attendee status does not match the attendee's view of their own status.
         """
         
-        print "\n---- Verifying Organizer events against Attendee copies ----"
+        self.output.write("\n---- Verifying Organizer events against Attendee copies ----\n")
         self.txn = self.store.newTransaction()
 
         results_missing = []
@@ -607,13 +636,13 @@
         for ctr, organizerEvent in enumerate(self.organized):
             
             if self.options["verbose"] and divmod(ctr, organizer_div)[1] == 0:
-                print "%d of %d (%d%%) Missing: %d  Mismatched: %s" % (
+                self.output.write("%d of %d (%d%%) Missing: %d  Mismatched: %s\n" % (
                     ctr+1,
                     organized_len,
                     ((ctr+1) * 100 / organized_len),
                     len(results_missing),
                     len(results_mismatch),
-                )
+                ))
 
             # To avoid holding locks on all the rows scanned, commit every 100 resources
             if divmod(ctr, 100)[1] == 0:
@@ -626,7 +655,7 @@
             if calendar is None:
                 continue
             if self.options["verbose"] and self.masterComponent(calendar) is None:
-                print "Missing master for organizer: %s, resid: %s, uid: %s" % (organizer, resid, uid,)
+                self.output.write("Missing master for organizer: %s, resid: %s, uid: %s\n" % (organizer, resid, uid,))
             organizerViewOfAttendees = self.buildAttendeeStates(calendar, self.start, self.end)
             try:
                 del organizerViewOfAttendees[organizer]
@@ -667,11 +696,11 @@
                                 results_mismatch.append((uid, resid, organizer, org_created, org_modified, organizerAttendee, att_created, att_modified))
                                 broken = True
                                 if self.options["details"]:
-                                    print "Mismatch: on Organizer's side:"
-                                    print "          UID: %s" % (uid,)
-                                    print "          Organizer: %s" % (organizer,)
-                                    print "          Attendee: %s" % (organizerAttendee,)
-                                    print "          Instance: %s" % (_organizerInstance,)
+                                    self.output.write("Mismatch: on Organizer's side:\n")
+                                    self.output.write("          UID: %s\n" % (uid,))
+                                    self.output.write("          Organizer: %s\n" % (organizer,))
+                                    self.output.write("          Attendee: %s\n" % (organizerAttendee,))
+                                    self.output.write("          Instance: %s\n" % (_organizerInstance,))
                                 break
                         # Check that the difference is only cancelled on the attendees side
                         for _attendeeInstance, partstat in attendeeOwnStatus.difference(organizerViewOfStatus):
@@ -680,10 +709,10 @@
                                     results_mismatch.append((uid, resid, organizer, org_created, org_modified, organizerAttendee, att_created, att_modified))
                                 broken = True
                                 if self.options["details"]:
-                                    print "Mismatch: on Attendee's side:"
-                                    print "          Organizer: %s" % (organizer,)
-                                    print "          Attendee: %s" % (organizerAttendee,)
-                                    print "          Instance: %s" % (_attendeeInstance,)
+                                    self.output.write("Mismatch: on Attendee's side:\n")
+                                    self.output.write("          Organizer: %s\n" % (organizer,))
+                                    self.output.write("          Attendee: %s\n" % (organizerAttendee,))
+                                    self.output.write("          Instance: %s\n" % (_attendeeInstance,))
                                 break
 
                 # Check that the status for this attendee is always declined which means a missing copy of the event is OK
@@ -755,7 +784,7 @@
         Make sure that for each attendee, there is a matching event for the organizer.
         """
 
-        print "\n---- Verifying Attendee events against Organizer copies ----"
+        self.output.write("\n---- Verifying Attendee events against Organizer copies ----\n")
         self.txn = self.store.newTransaction()
 
         # Now try to match up each attendee event
@@ -767,13 +796,13 @@
         for ctr, attendeeEvent in enumerate(self.attended):
             
             if self.options["verbose"] and divmod(ctr, attended_div)[1] == 0:
-                print "%d of %d (%d%%) Missing: %d  Mismatched: %s" % (
+                self.output.write("%d of %d (%d%%) Missing: %d  Mismatched: %s\n" % (
                     ctr+1,
                     attended_len,
                     ((ctr+1) * 100 / attended_len),
                     len(missing),
                     len(mismatched),
-                )
+                ))
 
             # To avoid holding locks on all the rows scanned, commit every 100 resources
             if divmod(ctr, 100)[1] == 0:
@@ -980,7 +1009,7 @@
         configuration, creating one first if necessary.
         """
         if self._directory is None:
-            self._directory = directoryFromConfig(self.config)
+            self._directory = getDirectory(self.config) #directoryFromConfig(self.config)
         return self._directory
 
 
@@ -1011,6 +1040,7 @@
         sys.exit(1)
     def makeService(store):
         from twistedcaldav.config import config
+        config.TransactionTimeoutSeconds = 0
         return CalVerifyService(store, options, output, reactor, config)
     utilityMain(options['config'], makeService, reactor)
 

Added: CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml	2012-04-12 16:57:54 UTC (rev 9039)
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2010 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.
+ -->
+
+<!DOCTYPE accounts SYSTEM "../../../conf/auth/accounts.dtd">
+
+<accounts realm="/Search">
+  <user>
+    <uid>example1</uid>
+    <guid>D46F3D71-04B7-43C2-A7B6-6F92F92E61D0</guid>
+    <password>example</password>
+    <name>Example User1</name>
+    <email-address>example1 at example.com</email-address>
+  </user>
+  <user>
+    <uid>example2</uid>
+    <guid>47B16BB4-DB5F-4BF6-85FE-A7DA54230F92</guid>
+    <password>example</password>
+    <name>Example User2</name>
+    <email-address>example2 at example.com</email-address>
+  </user>
+  <user>
+    <uid>example3</uid>
+    <guid>AC478592-7783-44D1-B2AE-52359B4E8415</guid>
+    <password>example</password>
+    <name>Example User3</name>
+    <email-address>example3 at example.com</email-address>
+  </user>
+</accounts>

Added: CalendarServer/trunk/calendarserver/tools/test/calverify/resources.xml
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/calverify/resources.xml	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/calverify/resources.xml	2012-04-12 16:57:54 UTC (rev 9039)
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<accounts realm="/Search">
+</accounts>


Property changes on: CalendarServer/trunk/calendarserver/tools/test/calverify/resources.xml
___________________________________________________________________
Added: svn:executable
   + *

Added: CalendarServer/trunk/calendarserver/tools/test/test_calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_calverify.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/test_calverify.py	2012-04-12 16:57:54 UTC (rev 9039)
@@ -0,0 +1,357 @@
+##
+# 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.
+##
+
+"""
+Tests for calendarserver.tools.calverify
+"""
+
+from StringIO import StringIO
+from calendarserver.tap.util import getRootResource
+from calendarserver.tools.calverify import CalVerifyService
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
+from twisted.trial import unittest
+from twistedcaldav.config import config
+from txdav.caldav.datastore import util
+from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom, CommonCommonTests
+import os
+
+
+OK_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:OK
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# Missing DTSTAMP
+BAD1_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD1
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# Bad recurrence
+BAD2_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD2
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+RRULE:FREQ=DAILY;COUNT=3
+SEQUENCE:2
+END:VEVENT
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD2
+RECURRENCE-ID:20000307T120000Z
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# Bad recurrence
+BAD3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD2
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+RRULE:FREQ=DAILY;COUNT=3
+SEQUENCE:2
+END:VEVENT
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD2
+RECURRENCE-ID:20000307T120000Z
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# Missing Organizer
+BAD3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD3
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+RRULE:FREQ=DAILY;COUNT=3
+SEQUENCE:2
+END:VEVENT
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD3
+RECURRENCE-ID:20000307T111500Z
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:mailto:example2 at example.com
+ATTENDEE:mailto:example1 at example.com
+ATTENDEE:mailto:example2 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# https Organizer
+BAD4_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD4
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+ORGANIZER:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:mailto:example1 at example.com
+ATTENDEE:mailto:example2 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
+# https Attendee
+BAD5_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD5
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+ORGANIZER:mailto:example1 at example.com
+ATTENDEE:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:mailto:example2 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
+# https Organizer and Attendee
+BAD6_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD6
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+ORGANIZER:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:mailto:example2 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
+class CalVerifyTests(CommonCommonTests, unittest.TestCase):
+    """
+    Tests for deleting events older than a given date
+    """
+
+    metadata = {
+        "accessMode": "PUBLIC",
+        "isScheduleObject": True,
+        "scheduleTag": "abc",
+        "scheduleEtags": (),
+        "hasPrivateComment": False,
+    }
+
+    requirements = {
+        "home1" : {
+            "calendar1" : {
+                "ok.ics" : (OK_ICS, metadata,),
+                "bad1.ics" : (BAD1_ICS, metadata,),
+                "bad2.ics" : (BAD2_ICS, metadata,),
+                "bad3.ics" : (BAD3_ICS, metadata,),
+                "bad4.ics" : (BAD4_ICS, metadata,),
+                "bad5.ics" : (BAD5_ICS, metadata,),
+                "bad6.ics" : (BAD6_ICS, metadata,),
+            }
+        },
+    }
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(CalVerifyTests, self).setUp()
+        self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
+        yield self.populate()
+
+        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
+
+
+    def verifyResultsByUID(self, results, expected):
+        reported = set([(home, uid) for home, uid, _ignore_resid, _ignore_reason in results])
+        self.assertEqual(reported, expected)
+
+
+    @inlineCallbacks
+    def test_scanBadData(self):
+        """
+        CalVerifyService.doScan without fix. Make sure it detects common errors.
+        """
+
+        options = {
+            "ical":None,
+            "verbose":False,
+            "uuid":"",
+        }
+        output = StringIO()
+        calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+        yield calverify.doScan(True, False, False)
+
+        self.assertEqual(calverify.results["Number of events to process"], 7)
+        self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
+            ("home1", "BAD1",),
+            ("home1", "BAD2",),
+            ("home1", "BAD3",),
+            ("home1", "BAD4",),
+            ("home1", "BAD5",),
+            ("home1", "BAD6",),
+        )))
+
+
+    @inlineCallbacks
+    def test_fixBadData(self):
+        """
+        CalVerifyService.doScan without fix. Make sure it detects and fixes as much as it can.
+        """
+
+        options = {
+            "ical":None,
+            "verbose":False,
+            "uuid":"",
+        }
+        output = StringIO()
+        
+        # Do fix
+        self.patch(config.Scheduling.Options, "PrincipalHostAliases", "demo.com")
+        self.patch(config, "HTTPPort", 8008)
+        calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+        yield calverify.doScan(True, False, True)
+
+        self.assertEqual(calverify.results["Number of events to process"], 7)
+        self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
+            ("home1", "BAD1",),
+            ("home1", "BAD2",),
+            ("home1", "BAD3",),
+            ("home1", "BAD4",),
+            ("home1", "BAD5",),
+            ("home1", "BAD6",),
+        )))
+
+        # Do scan
+        calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+        yield calverify.doScan(True, False, False)
+
+        self.assertEqual(calverify.results["Number of events to process"], 7)
+        self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
+            ("home1", "BAD1",),
+        )))

Modified: CalendarServer/trunk/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/util.py	2012-04-12 08:11:09 UTC (rev 9038)
+++ CalendarServer/trunk/txdav/caldav/datastore/util.py	2012-04-12 16:57:54 UTC (rev 9039)
@@ -44,6 +44,8 @@
 
 log = Logger()
 
+validationBypass = False
+
 def validateCalendarComponent(calendarObject, calendar, component, inserting, migrating):
     """
     Validate a calendar component for a particular calendar.
@@ -59,6 +61,9 @@
     @type component: L{VComponent}
     """
 
+    if validationBypass:
+        return
+
     if not isinstance(component, VComponent):
         raise TypeError(type(component))
 

Modified: CalendarServer/trunk/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/util.py	2012-04-12 08:11:09 UTC (rev 9038)
+++ CalendarServer/trunk/txdav/common/datastore/test/util.py	2012-04-12 16:57:54 UTC (rev 9039)
@@ -331,7 +331,7 @@
 
 
 @inlineCallbacks
-def populateCalendarsFrom(requirements, store):
+def populateCalendarsFrom(requirements, store, migrating=False):
     """
     Populate C{store} from C{requirements}.
 
@@ -341,6 +341,8 @@
     @param store: the L{IDataStore} to populate with calendar data.
     """
     populateTxn = store.newTransaction()
+    if migrating:
+        populateTxn._migrating = True
     for homeUID in requirements:
         calendars = requirements[homeUID]
         if calendars is not None:
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120412/3f2b8b92/attachment-0001.html>


More information about the calendarserver-changes mailing list