def test_objectresource_component(self): """ Test that a remote object resource L{component} works. """ home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True) self.assertTrue(home01 is not None) calendar01 = yield home01.childWithName("calendar") yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)) yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2)) yield self.commitTransaction(0) home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01") self.assertTrue(home is not None) calendar = yield home.childWithName("calendar") resource = yield calendar.objectResourceWithName("1.ics") caldata = yield resource.component() self.assertEqual(str(caldata), self.caldata1) resource = yield calendar.objectResourceWithName("2.ics") caldata = yield resource.component() self.assertEqual(str(caldata), self.caldata2) yield self.commitTransaction(1)
def test_oneEventCalendar(self): """ Exporting an calendar with one event in it will result in just that event. """ yield populateCalendarsFrom( { "home1": { "calendar1": { "valentines-day.ics": (valentines, {}) } } }, self.store ) expected = Component.newCalendar() [theComponent] = Component.fromString(valentines).subcomponents() expected.addComponent(theComponent) io = StringIO() yield exportToFile( [(yield self.txn().calendarHomeWithUID("home1")) .calendarWithName("calendar1")], io ) self.assertEquals(Component.fromString(io.getvalue()), expected)
def test_vcalendar_no_effect(self): data = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] ORGANIZER;CN=User 01:mailto:[email protected] END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") no_effect = CalendarData( CalendarComponent( name="VCALENDAR" ) ) for item in (data, Component.fromString(data),): self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)), data) no_effect = CalendarData( CalendarComponent( AllComponents(), AllProperties(), name="VCALENDAR" ) ) for item in (data, Component.fromString(data),): self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)), data)
def test_twoSimpleEvents(self): """ Exporting a calendar with two events in it will result in a VCALENDAR component with both VEVENTs in it. """ yield populateCalendarsFrom( { "home1": { "calendar1": { "valentines-day.ics": (valentines, {}), "new-years-day.ics": (newYears, {}) } } }, self.store ) expected = Component.newCalendar() a = Component.fromString(valentines) b = Component.fromString(newYears) for comp in a, b: for sub in comp.subcomponents(): expected.addComponent(sub) io = StringIO() yield exportToFile( [(yield self.txn().calendarHomeWithUID("home1")) .calendarWithName("calendar1")], io ) self.assertEquals(Component.fromString(io.getvalue()), expected)
def test_objectresource_objectwith(self): """ Test that a remote home child L{objectResourceWithName} and L{objectResourceWithUID} works. """ home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True) self.assertTrue(home01 is not None) calendar01 = yield home01.childWithName("calendar") resource01 = yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)) yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2)) yield self.commitTransaction(0) home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01") self.assertTrue(home is not None) calendar = yield home.childWithName("calendar") resource = yield calendar.objectResourceWithName("2.ics") self.assertEqual(resource.name(), "2.ics") resource = yield calendar.objectResourceWithName("foo.ics") self.assertEqual(resource, None) resource = yield calendar.objectResourceWithUID("uid1") self.assertEqual(resource.name(), "1.ics") resource = yield calendar.objectResourceWithUID("foo") self.assertEqual(resource, None) resource = yield calendar.objectResourceWithID(resource01.id()) self.assertEqual(resource.name(), "1.ics") resource = yield calendar.objectResourceWithID(12345) self.assertEqual(resource, None) yield self.commitTransaction(1)
def test_listobjects(self): """ Test that action=listobjects works. """ yield self.createShare("user01", "puser01") shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar") objects = yield shared.listObjectResources() self.assertEqual(set(objects), set()) yield self.commitTransaction(1) calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar") yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)) yield calendar1.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2)) objects = yield calendar1.listObjectResources() self.assertEqual(set(objects), set(("1.ics", "2.ics",))) yield self.commitTransaction(0) shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar") objects = yield shared.listObjectResources() self.assertEqual(set(objects), set(("1.ics", "2.ics",))) yield self.commitTransaction(1) calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar") object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics") yield object1.remove() objects = yield calendar1.listObjectResources() self.assertEqual(set(objects), set(("2.ics",))) yield self.commitTransaction(0) shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar") objects = yield shared.listObjectResources() self.assertEqual(set(objects), set(("2.ics",))) yield self.commitTransaction(1)
def migrate(self, txn, mapIDsCallback): """ See L{ScheduleWork.migrate} """ # Try to find a mapping new_home, new_resource = yield mapIDsCallback(self.resourceID) # If we previously had a resource ID and now don't, then don't create work if self.resourceID is not None and new_resource is None: returnValue(False) if self.icalendarTextOld: calendar_old = Component.fromString(self.icalendarTextOld) uid = calendar_old.resourceUID() else: calendar_new = Component.fromString(self.icalendarTextNew) uid = calendar_new.resourceUID() # Insert new work - in paused state yield ScheduleOrganizerWork.schedule( txn, uid, scheduleActionFromSQL[self.scheduleAction], new_home, new_resource, self.icalendarTextOld, self.icalendarTextNew, new_home.uid(), self.attendeeCount, self.smartMerge, pause=1 ) returnValue(True)
def doWork(self): """ Do the export, stopping the reactor when done. """ try: if self.options.inputDirectoryName: dirname = self.options.inputDirectoryName if not os.path.exists(dirname): sys.stderr.write( "Directory does not exist: {}\n".format(dirname) ) sys.exit(1) for filename in os.listdir(dirname): fullpath = os.path.join(dirname, filename) print("Importing {}".format(fullpath)) fileobj = open(fullpath, 'r') component = Component.allFromStream(fileobj) fileobj.close() yield importCollectionComponent(self.store, component) else: try: input = self.options.openInput() except IOError, e: sys.stderr.write( "Unable to open input file for reading: %s\n" % (e) ) sys.exit(1) component = Component.allFromStream(input) input.close() yield importCollectionComponent(self.store, component) except: log.failure("doWork()")
def test_objectresource_resourceuidforname(self): """ Test that a remote home child L{resourceUIDForName} works. """ home01 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True) self.assertTrue(home01 is not None) calendar01 = yield home01.childWithName("calendar") yield calendar01.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)) yield calendar01.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2)) yield self.commitTransaction(0) home = yield self._remoteHome(self.theTransactionUnderTest(1), "user01") self.assertTrue(home is not None) calendar = yield home.childWithName("calendar") uid = yield calendar.resourceUIDForName("1.ics") self.assertEqual(uid, "uid1") uid = yield calendar.resourceUIDForName("2.ics") self.assertEqual(uid, "uid2") uid = yield calendar.resourceUIDForName("foo.ics") self.assertEqual(uid, None) yield self.commitTransaction(1)
def init_perinstance_component(): peruser = Component(PerUserDataFilter.PERINSTANCE_COMPONENT) rid = component.getRecurrenceIDUTC() if rid: peruser.addProperty(Property("RECURRENCE-ID", rid)) perinstance_components[rid] = peruser return peruser
def test_full(self): """ Running C{calendarserver_export} on the command line exports an ics file. (Almost-full integration test, starting from the main point, using as few test fakes as possible.) Note: currently the only test for directory interaction. """ yield populateCalendarsFrom( { "user02": { # TODO: more direct test for skipping inbox "inbox": { "inbox-item.ics": (valentines, {}) }, "calendar1": { "peruser.ics": (dataForTwoUsers, {}), # EST } } }, self.store ) output = FilePath(self.mktemp()) main(['calendarserver_export', '--output', output.path, '--user', 'user02'], reactor=self) yield self.waitToStop self.assertEquals( Component.fromString(resultForUser2), Component.fromString(output.getContent()) )
def test_setcomponent(self): """ Test that action=setcomponent works. """ yield self.createShare("user01", "puser01") calendar1 = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(0), home="user01", name="calendar") yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)) yield self.commitTransaction(0) shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics") ical = yield shared_object.component() self.assertTrue(isinstance(ical, Component)) self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1)) yield self.commitTransaction(1) shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", calendar_name="shared-calendar", name="1.ics") changed = yield shared_object.setComponent(Component.fromString(self.caldata1_changed)) self.assertFalse(changed) ical = yield shared_object.component() self.assertTrue(isinstance(ical, Component)) self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed)) yield self.commitTransaction(1) object1 = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics") ical = yield object1.component() self.assertTrue(isinstance(ical, Component)) self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed)) yield self.commitTransaction(0)
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) organizerAddress = yield calendarUserFromCalendarUserUID(home.uid(), self.transaction) organizer = organizerAddress.record.canonicalCalendarUserAddress() calendar_old = Component.fromString(self.icalendarTextOld) if self.icalendarTextOld else None calendar_new = Component.fromString(self.icalendarTextNew) if self.icalendarTextNew else None log.debug("ScheduleOrganizerWork - running for ID: {id}, UID: {uid}, organizer: {org}", id=self.workID, uid=self.icalendarUid, org=organizer) # We need to get the UID lock for implicit processing. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(self.icalendarUid).hexdigest(),)) from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler scheduler = ImplicitScheduler() yield scheduler.queuedOrganizerProcessing( self.transaction, scheduleActionFromSQL[self.scheduleAction], home, resource, self.icalendarUid, calendar_old, calendar_new, self.smartMerge ) self._dequeued() except Exception, e: log.debug("ScheduleOrganizerWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=self.icalendarUid, err=str(e)) log.debug(traceback.format_exc()) raise
def test_full(self): """ Running C{calendarserver_export} on the command line exports an ics file. (Almost-full integration test, starting from the main point, using as few test fakes as possible.) Note: currently the only test for directory interaction. """ yield populateCalendarsFrom( { "user02": { # TODO: more direct test for skipping inbox "inbox": { "inbox-item.ics": (valentines, {}) }, "calendar1": { "peruser.ics": (dataForTwoUsers, {}), # EST } } }, self.store ) augmentsData = """ <augments> <record> <uid>Default</uid> <enable>true</enable> <enable-calendar>true</enable-calendar> <enable-addressbook>true</enable-addressbook> </record> </augments> """ augments = FilePath(self.mktemp()) augments.setContent(augmentsData) accountsData = """ <accounts realm="Test Realm"> <user> <uid>user-under-test</uid> <guid>user02</guid> <name>Not Interesting</name> <password>very-secret</password> </user> </accounts> """ accounts = FilePath(self.mktemp()) accounts.setContent(accountsData) output = FilePath(self.mktemp()) self.accountsFile = accounts.path self.augmentsFile = augments.path main(['calendarserver_export', '--output', output.path, '--user', 'user-under-test'], reactor=self) yield self.waitToStop self.assertEquals( Component.fromString(resultForUser2), Component.fromString(output.getContent()) )
def assertEqualCalendarData(self, cal1, cal2): if isinstance(cal1, str): cal1 = Component.fromString(cal1) if isinstance(cal2, str): cal2 = Component.fromString(cal2) ncal1 = normalize_iCalStr(cal1) ncal2 = normalize_iCalStr(cal2) self.assertEqual(ncal1, ncal2, msg=diff_iCalStrs(ncal1, ncal2))
def test_validation_replaceMissingToDoProperties_Completed(self): """ Test that VTODO completed status is fixed. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VTODO UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VTODO END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName("test.ics", calendar) yield self.commit() data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VTODO UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z SUMMARY:Changed COMPLETED:20080601T140000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VTODO END:VCALENDAR """ calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",)) calendar = Component.fromString(data2) txn = self.transactionUnderTest() txn._authz_uid = "user01" yield calendar_resource.setComponent(calendar) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",)) calendar1 = (yield calendar_resource.component()) calendar1 = str(calendar1).replace("\r\n ", "") self.assertTrue("ORGANIZER" in calendar1) self.assertTrue("ATTENDEE" in calendar1) self.assertTrue("SUMMARY:Changed" in calendar1) self.assertTrue("PARTSTAT=COMPLETED" in calendar1) yield self.commit()
def test_emptyCalendar(self): """ Exporting an empty calendar results in an empty calendar. """ io = StringIO() value = yield exportToFile([], io) # it doesn't return anything, it writes to the file. self.assertEquals(value, None) # but it should write a valid component to the file. self.assertEquals(Component.fromString(io.getvalue()), Component.newCalendar())
def test_ImportComponentNoScheduling(self): component = Component.allFromString(DATA_NO_SCHEDULING) yield importCollectionComponent(self.store, component) txn = self.store.newTransaction() home = yield txn.calendarHomeWithUID("user01") collection = yield home.childWithName("calendar") # Verify properties have been set collectionProperties = collection.properties() for element, value in ( (davxml.DisplayName, "Sample Import Calendar"), (customxml.CalendarColor, "#0E61B9FF"), ): self.assertEquals( value, collectionProperties[PropertyName.fromElement(element)] ) # Verify child objects objects = yield collection.listObjectResources() self.assertEquals(len(objects), 2) yield txn.commit() # Reimport different component into same collection component = Component.allFromString(DATA_NO_SCHEDULING_REIMPORT) yield importCollectionComponent(self.store, component) txn = self.store.newTransaction() home = yield txn.calendarHomeWithUID("user01") collection = yield home.childWithName("calendar") # Verify properties have been changed collectionProperties = collection.properties() for element, value in ( (davxml.DisplayName, "Sample Import Calendar Reimported"), (customxml.CalendarColor, "#FFFFFFFF"), ): self.assertEquals( value, collectionProperties[PropertyName.fromElement(element)] ) # Verify child objects (should be 3 now) objects = yield collection.listObjectResources() self.assertEquals(len(objects), 3) yield txn.commit()
def test_validation_preserveOrganizerPrivateComments(self): """ Test that organizer private comments are restored. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-organizer DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:x-uid:user01"; X-CALENDARSERVER-DTSTAMP=20131101T100000Z:Someone else's comment END:VEVENT END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName("test.ics", calendar) yield self.commit() data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-organizer DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z SUMMARY:Changed END:VEVENT END:VCALENDAR """ txn = self.transactionUnderTest() txn._authz_uid = "user01" calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",)) calendar = Component.fromString(data2) yield calendar_resource.setComponent(calendar) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",)) calendar1 = (yield calendar_resource.component()) calendar1 = str(calendar1).replace("\r\n ", "") self.assertTrue("X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF=\"urn:x-uid:user01\";X-CALENDARSERVER-DTSTAMP=20131101T100000Z:Someone else's comment" in calendar1) self.assertTrue("SUMMARY:Changed" in calendar1) yield self.commit()
def test_testImplicitSchedulingPUT_FixScheduleState(self): """ Test that testImplicitSchedulingPUT will fix an old cached schedule object state by re-evaluating the calendar data. """ calendarOld = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """) calendarNew = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """) calendar_collection = (yield self.calendarUnderTest(home="user01")) calresource = (yield calendar_collection.createCalendarObjectWithName( "1.ics", calendarOld )) calresource.isScheduleObject = False scheduler = ImplicitScheduler() try: doAction, isScheduleObject = (yield scheduler.testImplicitSchedulingPUT(calendar_collection, calresource, calendarNew, False)) except Exception as e: print e self.fail("Exception must not be raised") self.assertTrue(doAction) self.assertTrue(isScheduleObject)
def test_validation_noPreservePrivateComments(self): """ Test that attendee private comments are no longer restored. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z X-CALENDARSERVER-PRIVATE-COMMENT:My Comment END:VEVENT END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName("test.ics", calendar) yield self.commit() data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z SUMMARY:Changed END:VEVENT END:VCALENDAR """ txn = self.transactionUnderTest() txn._authz_uid = "user01" calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",)) calendar = Component.fromString(data2) yield calendar_resource.setComponent(calendar) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",)) calendar1 = (yield calendar_resource.component()) calendar1 = str(calendar1).replace("\r\n ", "") self.assertFalse("X-CALENDARSERVER-PRIVATE-COMMENT:My Comment" in calendar1) self.assertTrue("SUMMARY:Changed" in calendar1) yield self.commit()
def test_testImplicitSchedulingPUT_NoChangeScheduleState(self): """ Test that testImplicitSchedulingPUT will prevent attendees from changing the schedule object state. """ request = SimpleRequest(self.site, "PUT", "/calendar/1.ics") calresource = yield request.locateResource("/calendar/1.ics") self.assertEqual(calresource.isScheduleObject, None) calresource.isScheduleObject = False calendarOld = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT END:VCALENDAR """) calendarNew = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 02":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """) calresource.exists = lambda : True calresource.iCalendarForUser = lambda request: succeed(calendarOld) scheduler = ImplicitScheduler() try: yield scheduler.testImplicitSchedulingPUT(request, calresource, "/calendars/users/user01/calendar/1.ics", calendarNew, False) except HTTPError: pass except: self.fail("HTTPError exception must be raised") else: self.fail("Exception must be raised") request._newStoreTransaction.abort()
def test_testImplicitSchedulingPUT_FixScheduleState(self): """ Test that testImplicitSchedulingPUT will fix an old cached schedule object state by re-evaluating the calendar data. """ request = SimpleRequest(self.site, "PUT", "/calendar/1.ics") calresource = yield request.locateResource("/calendar/1.ics") self.assertEqual(calresource.isScheduleObject, None) calresource.isScheduleObject = False calendarOld = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 02":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """) calendarNew = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 02":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """) calresource.exists = lambda : True calresource.iCalendarForUser = lambda request: succeed(calendarOld) scheduler = ImplicitScheduler() try: doAction, isScheduleObject = (yield scheduler.testImplicitSchedulingPUT(request, calresource, "/calendars/users/user01/calendar/1.ics", calendarNew, False)) except: self.fail("Exception must not be raised") self.assertTrue(doAction) self.assertTrue(isScheduleObject)
def test_testImplicitSchedulingPUT_NoChangeScheduleState(self): """ Test that testImplicitSchedulingPUT will prevent attendees from changing the schedule object state. """ calendarOld = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT END:VCALENDAR """) calendarNew = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 02":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """) calendar_collection = (yield self.calendarUnderTest(home="user01")) calresource = (yield calendar_collection.createCalendarObjectWithName( "1.ics", calendarOld )) calresource.isScheduleObject = False scheduler = ImplicitScheduler() try: yield scheduler.testImplicitSchedulingPUT(calendar_collection, calresource, calendarNew, False) except HTTPError: pass except: self.fail("HTTPError exception must be raised") else: self.fail("Exception must be raised")
def test_doCreateResource(self): """ Test that resource creation works. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar1 = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName("test.ics", calendar1) yield self.commit() calendar_resource1 = (yield self.calendarObjectUnderTest(name="test.ics", home="user01",)) calendar1 = (yield calendar_resource1.component()) calendar1 = str(calendar1).replace("\r\n ", "") self.assertTrue("urn:uuid:user01" in calendar1) self.assertTrue("urn:uuid:user02" in calendar1) self.assertTrue("CN=" in calendar1) yield self.commit()
def test_cancelAsAttendeeMultipleOccurrences(self): # Multiple meeting occurrences with no master, where purged CUA is # an attendee event = Component.fromString(INVITED_TO_MULTIPLE_OCCURRENCES_ICS) action = PurgePrincipalService._cancelEvent(event, PyCalendarDateTime(2010, 12, 6, 12, 0, 0, PyCalendarTimezone(utc=True)), "urn:uuid:9DC04A71-E6DD-11DF-9492-0800200C9A66") self.assertEquals(action, PurgePrincipalService.CANCELEVENT_SHOULD_DELETE)
def test_validation_validAttendeeListSizeCheck(self): """ Test that resource with too many attendees are rejected. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """ self.patch(config, "MaxAttendeesPerInstance", 2) calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield self.failUnlessFailure(calendar_collection.createCalendarObjectWithName("test.ics", calendar), TooManyAttendeesError) yield self.commit()
def test_queueAttendeeUpdate_with_refresh(self): self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", 5) calendar = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER:urn:uuid:user01 ATTENDEE:urn:uuid:user01 ATTENDEE:urn:uuid:user02 ATTENDEE:urn:uuid:user03 END:VEVENT END:VCALENDAR """) processor = FakeImplicitProcessor() processor.txn = "" processor.uid = "12345-67890" processor.recipient_calendar_resource = FakeResource() processor.recipient_calendar = calendar yield processor.queueAttendeeUpdate(("urn:uuid:user02", "urn:uuid:user01",)) self.assertEqual(processor.batches, 1)
def test_cancelAllDayRepeating(self): # A repeating All Day event where purged CUA is organizer event = Component.fromString(REPEATING_2_ICS_BEFORE) action = PurgePrincipalService._cancelEvent(event, PyCalendarDateTime(2010, 12, 6, 12, 0, 0, PyCalendarTimezone(utc=True)), "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1") self.assertEquals(action, PurgePrincipalService.CANCELEVENT_MODIFIED) self.assertEquals(str(event), REPEATING_2_ICS_AFTER)
def test_no_freebusy(self): data = """BEGIN:VCALENDAR VERSION:2.0 METHOD:REQUEST PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """ scheduler = iMIPProcessing.FakeSchedule(Component.fromString(data)) recipients = (RemoteCalendarUser("mailto:[email protected]"),) responses = ScheduleResponseQueue("REQUEST", responsecode.OK) delivery = ScheduleViaIMip(scheduler, recipients, responses, True) yield delivery.generateSchedulingResponses() self.assertEqual(len(responses.responses), 1) self.assertEqual(str(responses.responses[0].children[1]), iTIPRequestStatus.SERVICE_UNAVAILABLE)
def _calendarAvailabilityUpgrade_setup(self): av1 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN BEGIN:VAVAILABILITY ORGANIZER:mailto:[email protected] UID:[email protected] DTSTAMP:20061005T133225Z DTEND:20140101T000000Z BEGIN:AVAILABLE UID:[email protected] DTSTAMP:20061005T133225Z SUMMARY:Monday to Friday from 9:00 to 17:00 DTSTART:20130101T090000Z DTEND:20130101T170000Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """) av2 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN BEGIN:VAVAILABILITY ORGANIZER:mailto:[email protected] UID:[email protected] DTSTAMP:20061005T133225Z DTEND:20140101T000000Z BEGIN:AVAILABLE UID:[email protected] DTSTAMP:20061005T133225Z SUMMARY:Monday to Friday from 12:00 to 17:00 DTSTART:20130101T120000Z DTEND:20130101T170000Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """) user_details = ( ("user01", av1), ("user02", av2), ("user03", None), ) # Set dead properties on calendars for user, av in user_details: calendar = (yield self.calendarUnderTest(name="inbox", home=user)) if av: calendar.properties()[PropertyName.fromElement( customxml.CalendarAvailability )] = customxml.CalendarAvailability.fromString(str(av)) # Force data version to previous home = (yield self.homeUnderTest(name=user)) ch = home._homeSchema yield Update( { ch.DATAVERSION: 4 }, Where=ch.RESOURCE_ID == home._resourceID, ).on(self.transactionUnderTest()) yield self.commit() for user, av in user_details: home = (yield self.homeUnderTest(name=user)) calendar = (yield self.calendarUnderTest(name="inbox", home=user)) self.assertEqual(home.getAvailability(), None) self.assertEqual( PropertyName.fromElement(customxml.CalendarAvailability) in calendar.properties(), av is not None) yield self.commit() returnValue(user_details)
def calendar(self): """ Returns a calendar component derived from this element. """ return iComponent.fromString(str(self))
class XML(twistedcaldav.test.util.TestCase): """ XML tests """ calendar_file = os.path.join(os.path.dirname(__file__), "data", "Holidays", "C3184A66-1ED0-11D9-A5E0-000A958A3252.ics") calendar = Component.fromStream(file(calendar_file)) calendar.validCalendarData() calendar.validCalendarForCalDAV(methodAllowed=False) def test_ComponentFilter(self): """ Component filter element. """ for component_name, has in ( ("VEVENT", True), ("VTODO", False), ): if has: no = "no " else: no = "" if has != storeComponentFilter( ComponentFilter(ComponentFilter(name=component_name), name="VCALENDAR")).match( self.calendar, None): self.fail("Calendar has %s%s?" % (no, component_name)) def test_PropertyFilter(self): """ Property filter element. """ for property_name, has in ( ("UID", True), ("BOOGER", False), ): if has: no = "no " else: no = "" if has != storeComponentFilter( ComponentFilter(ComponentFilter( PropertyFilter(name=property_name), name="VEVENT"), name="VCALENDAR")).match( self.calendar, None): self.fail("Calendar has %sVEVENT with %s?" % (no, property_name)) def test_ParameterFilter(self): """ Parameter filter element. """ raise SkipTest("test unimplemented") def test_TextMatch(self): """ Text match element. """ for uid, caseless, has in ( ("C3184A66-1ED0-11D9-A5E0-000A958A3252", False, True), ("c3184a66-1ed0-11d9-a5e0-000a958a3252", True, True), ("BOOGER", False, False), ("BOOGER", True, False), ): if has: no = "no " else: no = "" if has != storeComponentFilter( ComponentFilter(ComponentFilter(PropertyFilter( TextMatch.fromString(uid, caseless=caseless), name="UID"), name="VEVENT"), name="VCALENDAR")).match( self.calendar, None): self.fail("Calendar has %sVEVENT with UID %s? (caseless=%s)" % (no, uid, caseless)) def test_TimeRange(self): """ Time range match element. """ for start, end, has in ( ("20020101T000000Z", "20020101T000001Z", True), ("20020101T000000Z", "20020101T000000Z", True), # Timespan of zero duration ("20020101", "20020101", True), # Timespan of zero duration ("20020101", "20020102", True), ("20020101", "20020103", True), ("20020102", "20020103", False), ("20011201", "20020101", False), # End is non-inclusive # Expanded recurrence ("20030101T000000Z", "20030101T000001Z", True), ("20030101T000000Z", "20030101T000000Z", True), # Timespan of zero duration ("20030101", "20030101", True), # Timespan of zero duration ("20030101", "20030102", True), ("20030101", "20030103", True), ("20030102", "20030103", False), ("20021201", "20030101", False), # End is non-inclusive ): if has: no = "no " else: no = "" if has != storeFilter( Filter( ComponentFilter( ComponentFilter(TimeRange(start=start, end=end), name="VEVENT"), name="VCALENDAR"))).match(self.calendar): self.fail("Calendar has %sVEVENT with timerange %s?" % (no, (start, end))) test_TimeRange.todo = "recurrence expansion"
def test_validation_replaceMissingToDoProperties_Completed(self): """ Test that VTODO completed status is fixed. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VTODO UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VTODO END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName( "test.ics", calendar) yield self.commit() data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VTODO UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z SUMMARY:Changed COMPLETED:20080601T140000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VTODO END:VCALENDAR """ txn = self.transactionUnderTest() txn._authz_uid = "user01" calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data2) yield calendar_resource.setComponent(calendar) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar1 = (yield calendar_resource.component()) calendar1 = str(calendar1).replace("\r\n ", "") self.assertTrue("ORGANIZER" in calendar1) self.assertTrue("ATTENDEE" in calendar1) self.assertTrue("SUMMARY:Changed" in calendar1) self.assertTrue("PARTSTAT=COMPLETED" in calendar1) yield self.commit()
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID( self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) itipmsg = Component.fromString(self.itipMsg) organizerAddress = yield calendarUserFromCalendarUserUID( home.uid(), self.transaction) organizer = organizerAddress.record.canonicalCalendarUserAddress() log.debug( "ScheduleOrganizerSendWork - running for ID: {id}, UID: {uid}, organizer: {org}, attendee: {att}", id=self.workID, uid=self.icalendarUID, org=organizer, att=self.attendee) # We need to get the UID lock for implicit processing. yield NamedLock.acquire( self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(self.icalendarUID).hexdigest(), )) from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler scheduler = ImplicitScheduler() yield scheduler.queuedOrganizerSending( self.transaction, scheduleActionFromSQL[self.scheduleAction], home, resource, self.icalendarUID, organizer, self.attendee, itipmsg, self.noRefresh) # Handle responses - update the actual resource in the store. Note that for a create the resource did not previously # exist and is stored as None for the work item, but the scheduler will attempt to find the new resources and use # that. We need to grab the scheduler's resource for further processing. resource = scheduler.resource if resource is not None: responses, all_delivered = self.extractSchedulingResponse( scheduler.queuedResponses) if not all_delivered: # Check for all connection failed yield self.checkTemporaryFailure(responses) # Update calendar data to reflect error status calendar = (yield resource.componentForUser()) changed = self.handleSchedulingResponse( responses, calendar, True) if changed: yield resource._setComponentInternal( calendar, internal_state=ComponentUpdateState. ORGANIZER_ITIP_UPDATE) self._dequeued() except Exception, e: log.debug( "ScheduleOrganizerSendWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=self.icalendarUID, err=str(e)) log.debug(traceback.format_exc()) raise
def test_validation_deleteWithDuplicatePrivateComments(self): """ Test that attendee private comments are no longer restored. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """ data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid: user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid: user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment END:VEVENT END:VCALENDAR """ data3 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE;PARTSTAT=ACCEPTED:mailto:[email protected] END:VEVENT END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName( "test.ics", calendar) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data2) yield calendar_resource._setComponentInternal( calendar, internal_state=ComponentUpdateState.RAW) yield self.commit() def raiseHere(otherself, component, inserting, internal_state): if component.hasDuplicatePrivateComments(doFix=False): raise ValueError self.patch(CalendarObject, "preservePrivateComments", raiseHere) calendar2 = (yield self.calendarUnderTest(name="calendar_1", home="user02")) cobjs = (yield calendar2.calendarObjects()) self.assertTrue(len(cobjs) == 1) yield cobjs[0].setComponent(Component.fromString(data3)) yield self.commit() calendar2 = (yield self.calendarUnderTest(name="calendar_1", home="user02")) cobjs = (yield calendar2.calendarObjects()) calendar = yield cobjs[0].component() self.assertTrue('SCHEDULE-STATUS=5.0' in normalize_iCalStr(calendar)) yield self.commit() calendar2 = (yield self.calendarUnderTest(name="calendar_1", home="user02")) cobjs = (yield calendar2.calendarObjects()) self.assertTrue(len(cobjs) == 1) yield cobjs[0].remove() yield self.commit() calendar2 = (yield self.calendarUnderTest(name="calendar_1", home="user02")) cobjs = (yield calendar2.calendarObjects()) self.assertTrue(len(cobjs) == 0) yield self.commit() self.flushLoggedErrors(ValueError)
def test_validation_validAccess_authzChangeNotAllowed(self): """ Test that resource access mode changes are rejected. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN X-CALENDARSERVER-ACCESS:PRIVATE BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT END:VCALENDAR """ self.patch(config, "EnablePrivateEvents", True) txn = self.transactionUnderTest() txn._authz_uid = "user02" calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield self.failUnlessFailure( calendar_collection.createCalendarObjectWithName( "test.ics", calendar), InvalidCalendarAccessError) yield self.commit() # This one should be OK txn = self.transactionUnderTest() txn._authz_uid = "user01" calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName( "test.ics", calendar) yield self.commit() # This one should re-insert access mode data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z SUMMARY:Changed END:VEVENT END:VCALENDAR """ txn = self.transactionUnderTest() txn._authz_uid = "user01" calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data2) yield calendar_resource.setComponent(calendar) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar1 = (yield calendar_resource.component()) calendar1 = str(calendar1).replace("\r\n ", "") self.assertTrue("X-CALENDARSERVER-ACCESS:PRIVATE" in calendar1) self.assertTrue("SUMMARY:Changed" in calendar1) yield self.commit()
def test_validation_duplicatePrivateCommentsOKWIthiTIP(self): """ Test that an iTIP update to an organizer event with duplicate private comments does not fail. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """ data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid: user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid: user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment END:VEVENT END:VCALENDAR """ data3 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE;PARTSTAT=ACCEPTED:mailto:[email protected] X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid: user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF="urn:uuid: user02";X-CALENDARSERVER-DTSTAMP=20140224T181133Z:Comment END:VEVENT END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName( "test.ics", calendar) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data2) yield calendar_resource._setComponentInternal( calendar, internal_state=ComponentUpdateState.RAW) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data3) yield calendar_resource._setComponentInternal( calendar, internal_state=ComponentUpdateState.ORGANIZER_ITIP_UPDATE) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data3) yield self.failUnlessFailure(calendar_resource.setComponent(calendar), DuplicatePrivateCommentsError) yield self.commit()
def _calendarTimezoneUpgrade_setup(self): tz1 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN BEGIN:VTIMEZONE TZID:Etc/GMT+1 X-LIC-LOCATION:Etc/GMT+1 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+1 TZOFFSETFROM:-0100 TZOFFSETTO:-0100 END:STANDARD END:VTIMEZONE END:VCALENDAR """) tz2 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN BEGIN:VTIMEZONE TZID:Etc/GMT+2 X-LIC-LOCATION:Etc/GMT+2 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+2 TZOFFSETFROM:-0200 TZOFFSETTO:-0200 END:STANDARD END:VTIMEZONE END:VCALENDAR """) tz3 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN BEGIN:VTIMEZONE TZID:Etc/GMT+3 X-LIC-LOCATION:Etc/GMT+3 BEGIN:STANDARD DTSTART:18000101T000000 RDATE:18000101T000000 TZNAME:GMT+3 TZOFFSETFROM:-0300 TZOFFSETTO:-0300 END:STANDARD END:VTIMEZONE END:VCALENDAR """) # Share user01 calendar with user03 calendar = (yield self.calendarUnderTest(name="calendar_1", home="user01")) home3 = yield self.homeUnderTest(name="user03") shared_name = yield calendar.shareWith(home3, _BIND_MODE_WRITE) user_details = ( ("user01", "calendar_1", tz1), ("user02", "calendar_1", tz2), ("user03", "calendar_1", None), ("user03", shared_name, tz3), ) # Set dead properties on calendars for user, calname, tz in user_details: calendar = (yield self.calendarUnderTest(name=calname, home=user)) if tz: calendar.properties()[PropertyName.fromElement( caldavxml.CalendarTimeZone )] = caldavxml.CalendarTimeZone.fromString(str(tz)) # Force data version to previous home = (yield self.homeUnderTest(name=user)) ch = home._homeSchema yield Update( { ch.DATAVERSION: 4 }, Where=ch.RESOURCE_ID == home._resourceID, ).on(self.transactionUnderTest()) yield self.commit() for user, calname, tz in user_details: calendar = (yield self.calendarUnderTest(name=calname, home=user)) self.assertEqual(calendar.getTimezone(), None) self.assertEqual( PropertyName.fromElement(caldavxml.CalendarTimeZone) in calendar.properties(), tz is not None) yield self.commit() # Create "fake" entry for non-existent share txn = self.transactionUnderTest() calendar = (yield self.calendarUnderTest(name="calendar_1", home="user01")) rp = schema.RESOURCE_PROPERTY yield Insert({ rp.RESOURCE_ID: calendar._resourceID, rp.NAME: PropertyName.fromElement(caldavxml.CalendarTimeZone).toString(), rp.VALUE: caldavxml.CalendarTimeZone.fromString(str(tz3)).toxml(), rp.VIEWER_UID: "user04", }).on(txn) yield self.commit() returnValue(user_details)
class Tasker(ProfileBase): """ A Calendar user who creates new tasks. """ _taskTemplate = Component.fromString("""\ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//iCal 4.0.3//EN CALSCALE:GREGORIAN BEGIN:VTODO CREATED:20101018T155431Z UID:C98AD237-55AD-4F7D-9009-0D355D835822 SUMMARY:Simple task DUE;TZID=America/New_York:20101021T120000 DTSTAMP:20101018T155438Z END:VTODO END:VCALENDAR """.replace("\n", "\r\n")) def setParameters( self, enabled=True, interval=25, taskDueDistribution=NearFutureDistribution(), ): self.enabled = enabled self._interval = interval self._taskStartDistribution = taskDueDistribution def run(self): self._call = LoopingCall(self._addTask) self._call.clock = self._reactor self._reactor.callLater( self.random.randint(1, self._interval), self._call.start, self._interval ) return Deferred() def _addTask(self): # Don't perform any operations until the client is up and running if not self._client.started: return succeed(None) calendars = self._calendarsOfType(caldavxml.calendar, "VTODO") while calendars: calendar = self.random.choice(calendars) calendars.remove(calendar) # Copy the template task and fill in some of its fields # to make a new task to create on the calendar. vcalendar = self._taskTemplate.duplicate() vtodo = vcalendar.mainComponent() uid = str(uuid4()) due = self._taskStartDistribution.sample() vtodo.replaceProperty(Property("CREATED", DateTime.getNowUTC())) vtodo.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC())) vtodo.replaceProperty(Property("DUE", due)) vtodo.replaceProperty(Property("UID", uid)) href = '%s%s.ics' % (calendar.url, uid) d = self._client.addEvent(href, vcalendar) return self._newOperation("create", d)
def test_calendarObjectRevisions_Modified(self): """ Verify that a calendar object created before the revision cut-off, but modified after it is correctly reported as changed after revision clean-up """ # Need to add one non-event change that creates a revision after the last event change revisions in order # for the logic in this test to work correctly home = yield self.homeUnderTest(name="user01") yield home.createCalendarWithName("_ignore_me") yield self.commit() # get initial sync token calendar = yield self.calendarUnderTest(home="user01", name="calendar") initial_token = yield calendar.syncToken() yield self.commit() # Pause to give some space in the modified time time.sleep(1) modified = datetime.datetime.utcnow() time.sleep(1) # Patch the work item to use the modified cut-off we need def _dateCutoff(self): return modified self.patch(FindMinValidRevisionWork, "dateCutoff", _dateCutoff) # Make a change to get a pre-update token cal2Object = yield self.calendarObjectUnderTest( self.transactionUnderTest(), name="cal2.ics", calendar_name="calendar", home="user01") yield cal2Object.remove() yield self.commit() # get changed sync token calendar = yield self.calendarUnderTest(home="user01", name="calendar") pre_update_token = yield calendar.syncToken() yield self.commit() # make changes cal1Object = yield self.calendarObjectUnderTest( self.transactionUnderTest(), name="cal1.ics", calendar_name="calendar", home="user01") yield cal1Object.setComponent(Component.fromString(self.cal1_mod)) yield self.commit() # get changed sync token calendar = yield self.calendarUnderTest(home="user01", name="calendar") update_token = yield calendar.syncToken() yield self.commit() # do FindMinValidRevisionWork and RevisionCleanupWork yield FindMinValidRevisionWork.reschedule(self.transactionUnderTest(), 0) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) # initial sync token fails calendar = yield self.calendarUnderTest(home="user01", name="calendar") yield self.failUnlessFailure( calendar.resourceNamesSinceToken(initial_token), SyncTokenValidException) yield self.commit() # Pre-update sync token returns one item calendar = yield self.calendarUnderTest(home="user01", name="calendar") names = yield calendar.resourceNamesSinceToken(pre_update_token) self.assertEqual(names, (['cal1.ics'], [], [])) yield self.commit() # Post-update sync token returns one item calendar = yield self.calendarUnderTest(home="user01", name="calendar") names = yield calendar.resourceNamesSinceToken(update_token) self.assertEqual(names, ([], [], [])) yield self.commit()
def test_validation_processAlarms_DuplicateRemoval(self): """ Test that duplicate alarms are removed. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName( "test.ics", calendar) yield self.commit() data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z SUMMARY:Changed BEGIN:VALARM X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF DESCRIPTION:Event reminder TRIGGER:-PT8M ACTION:DISPLAY END:VALARM BEGIN:VALARM X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF DESCRIPTION:Event reminder TRIGGER:-PT8M ACTION:DISPLAY END:VALARM END:VEVENT END:VCALENDAR """ txn = self.transactionUnderTest() txn._authz_uid = "user01" calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data2) result = (yield calendar_resource.setComponent(calendar)) yield self.commit() self.assertTrue(result) calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar1 = (yield calendar_resource.component()) calendar1 = str(calendar1).replace("\r\n ", "") self.assertEqual(calendar1.count("BEGIN:VALARM"), 1) self.assertTrue("SUMMARY:Changed" in calendar1) yield self.commit()
def test_migrateMergeConflictingObjects(self): """ When merging two homes together, calendar objects may conflict in the following ways: First, an object may have the same name and the same UID as an object in the target calendar. We assume the target object is always be newer than the source object, so this type of conflict will leave the source object unmodified. This type of conflict is expected, and may happen as a result of an implicitly scheduled event where the principal owning the merged calendars is an attendee of the conflicting object, and received a re-invitation. Second, an object may have a different name, but the same UID as an object in the target calendar. While this type of conflict is not expected -- most clients will choose names for objects that correspond to the iCalendar UIDs of their main component -- it is treated the same way as the first conflict. Third, an object may have the same UID as an object on a different calendar in the target home. This may also happen if a scheduled event was previously on a different (most likely non-default) calendar. Technically this is actually valid, and it is possible to have the same object in multiple calendars as long as the object is not scheduled; however, that type of conflict is extremely unlikely as the client would have to generate the same event twice. Basically, in all expected cases, conflicts will only occur because an update to a scheduled event was sent out and the target home accepted it. Therefore, conflicts are always resolved in favor of ignoring the source data and trusting that the target data is more reliable. """ # Note: these tests are all performed with un-scheduled data because it # is simpler. Although the expected conflicts will involve scheduled # data the behavior will be exactly the same. yield self.createConflicted( { "same-name": self.sampleEvent("same-name", "source"), "other-name": self.sampleEvent("other-uid", "source other"), "other-calendar": self.sampleEvent("oc", "source calendar"), "no-conflict": self.sampleEvent("no-conflict", "okay"), }, { "same-name": self.sampleEvent("same-name", "target"), "different-name": self.sampleEvent("other-uid", "tgt other"), }, ) txn = self.transactionUnderTest() c2 = yield txn.calendarHomeWithUID("conflict2") otherCal = yield c2.createCalendarWithName("othercal") yield otherCal.createCalendarObjectWithName( "some-name", Component.fromString(self.sampleEvent("oc", "target calendar")[0])) yield self.commit() txn = self.transactionUnderTest() c1 = yield txn.calendarHomeWithUID("conflict1") c2 = yield txn.calendarHomeWithUID("conflict2") yield migrateHome(c1, c2, merge=True) yield self.commit() txn = self.transactionUnderTest() c2 = yield txn.calendarHomeWithUID("conflict2") targetCal = yield c2.calendarWithName("conflicted") yield self.checkSummary("same-name", "target", targetCal) yield self.checkSummary("different-name", "tgt other", targetCal) yield self.checkSummary("other-calendar", None, targetCal) yield self.checkSummary("other-name", None, targetCal) yield self.checkSummary("no-conflict", "okay", targetCal) yield self.checkSummary("oc", "target calendar", otherCal)
def test_validation_mergePerUserData(self): """ Test that per-user data is correctly stored and retrieved. """ calendar_collection = (yield self.calendarUnderTest(home="user01")) sharee_home = (yield self.homeUnderTest(name="user02")) shared_name = (yield calendar_collection.shareWith( sharee_home, _BIND_MODE_WRITE, )) yield self.commit() data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z BEGIN:VALARM X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF DESCRIPTION:Event reminder TRIGGER:-PT5M ACTION:DISPLAY END:VALARM END:VEVENT END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName( "test.ics", calendar) yield self.commit() data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z BEGIN:VALARM X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF DESCRIPTION:Event reminder TRIGGER:-PT10M ACTION:DISPLAY END:VALARM END:VEVENT END:VCALENDAR """ calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", calendar_name=shared_name, home="user02", )) calendar = Component.fromString(data2) yield calendar_resource.setComponent(calendar) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) # Unfiltered view of event calendar1 = (yield calendar_resource.component()) calendar1 = str(calendar1).replace("\r\n ", "") self.assertTrue("TRIGGER:-PT5M" in calendar1) self.assertTrue("TRIGGER:-PT10M" in calendar1) self.assertEqual(calendar1.count("BEGIN:VALARM"), 2) # user01 view of event calendar1 = (yield calendar_resource.componentForUser("user01")) calendar1 = str(calendar1).replace("\r\n ", "") self.assertTrue("TRIGGER:-PT5M" in calendar1) self.assertFalse("TRIGGER:-PT10M" in calendar1) self.assertEqual(calendar1.count("BEGIN:VALARM"), 1) # user02 view of event calendar1 = (yield calendar_resource.componentForUser("user02")) calendar1 = str(calendar1).replace("\r\n ", "") self.assertFalse("TRIGGER:-PT5M" in calendar1) self.assertTrue("TRIGGER:-PT10M" in calendar1) self.assertEqual(calendar1.count("BEGIN:VALARM"), 1) yield self.commit() calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", calendar_name=shared_name, home="user02", )) # Unfiltered view of event calendar1 = (yield calendar_resource.component()) calendar1 = str(calendar1).replace("\r\n ", "") self.assertTrue("TRIGGER:-PT5M" in calendar1) self.assertTrue("TRIGGER:-PT10M" in calendar1) self.assertEqual(calendar1.count("BEGIN:VALARM"), 2) # user01 view of event calendar1 = (yield calendar_resource.componentForUser("user01")) calendar1 = str(calendar1).replace("\r\n ", "") self.assertTrue("TRIGGER:-PT5M" in calendar1) self.assertFalse("TRIGGER:-PT10M" in calendar1) self.assertEqual(calendar1.count("BEGIN:VALARM"), 1) # user02 view of event calendar1 = (yield calendar_resource.componentForUser("user02")) calendar1 = str(calendar1).replace("\r\n ", "") self.assertFalse("TRIGGER:-PT5M" in calendar1) self.assertTrue("TRIGGER:-PT10M" in calendar1) self.assertEqual(calendar1.count("BEGIN:VALARM"), 1) yield self.commit()
def test_outbound(self): """ Make sure outbound( ) stores tokens properly so they can be looked up """ config.Scheduling.iMIP.Sending.Address = "*****@*****.**" self.patch(config.Localization, "LocalesDirectory", os.path.join(os.path.dirname(__file__), "locales")) self._actualGenerateEmail = self.sender.generateEmail self.patch(self.sender, "generateEmail", self._interceptEmail) data = ( # Initial invite ( initialInviteText, "CFDD5E46-4F74-478A-9311-B3FF905449C3", "urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A", "mailto:[email protected]", "new", "*****@*****.**", u"Th\xe9 Organizer", [ (u'Th\xe9 Attendee', u'*****@*****.**'), (u'Th\xe9 Organizer', u'*****@*****.**'), (u'An Attendee without CUTYPE', u'*****@*****.**'), (None, u'*****@*****.**'), ], u"Th\xe9 Organizer <*****@*****.**>", "=?utf-8?q?Th=C3=A9_Organizer_=3Corganizer=40example=2Ecom=3E?=", "*****@*****.**", "Event invitation: testing outbound( ) Embedded: Header", ), # Update ( u"""BEGIN:VCALENDAR VERSION:2.0 METHOD:REQUEST BEGIN:VEVENT UID:CFDD5E46-4F74-478A-9311-B3FF905449C3 DTSTART:20100325T154500Z DTEND:20100325T164500Z ATTENDEE;CN=Th\xe9 Attendee;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;RSVP=TRUE: mailto:[email protected] ATTENDEE;CN=Th\xe9 Organizer;CUTYPE=INDIVIDUAL;[email protected];PAR TSTAT=ACCEPTED:urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A ORGANIZER;CN=Th\xe9 Organizer;[email protected]:urn:uuid:C3B38B00-41 66-11DD-B22C-A07C87E02F6A SUMMARY:t\xe9sting outbound( ) *update* END:VEVENT END:VCALENDAR """.encode("utf-8"), "CFDD5E46-4F74-478A-9311-B3FF905449C3", "urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A", "mailto:[email protected]", "update", "*****@*****.**", u"Th\xe9 Organizer", [(u'Th\xe9 Attendee', u'*****@*****.**'), (u'Th\xe9 Organizer', u'*****@*****.**')], u"Th\xe9 Organizer <*****@*****.**>", "=?utf-8?q?Th=C3=A9_Organizer_=3Corganizer=40example=2Ecom=3E?=", "*****@*****.**", "=?utf-8?q?Event_update=3A_t=C3=A9sting_outbound=28_=29_*update*?=", ), # Reply ( u"""BEGIN:VCALENDAR VERSION:2.0 METHOD:REPLY BEGIN:VEVENT UID:DFDD5E46-4F74-478A-9311-B3FF905449C4 DTSTART:20100325T154500Z DTEND:20100325T164500Z ATTENDEE;CN=Th\xe9 Attendee;CUTYPE=INDIVIDUAL;[email protected];PARTST AT=ACCEPTED:urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A ORGANIZER;CN=Th\xe9 Organizer;[email protected]:mailto:organizer@exam ple.com SUMMARY:t\xe9sting outbound( ) *reply* END:VEVENT END:VCALENDAR """.encode("utf-8"), None, "urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A", "mailto:[email protected]", "reply", "*****@*****.**", u"Th\xe9 Organizer", [ (u'Th\xe9 Attendee', u'*****@*****.**'), ], "*****@*****.**", "*****@*****.**", "*****@*****.**", "=?utf-8?q?Event_reply=3A_t=C3=A9sting_outbound=28_=29_*reply*?=", ), ) for (inputCalendar, UID, inputOriginator, inputRecipient, inviteState, outputOrganizerEmail, outputOrganizerName, outputAttendeeList, outputFrom, encodedFrom, outputRecipient, outputSubject) in data: txn = self.store.newTransaction() yield self.sender.outbound(txn, inputOriginator, inputRecipient, Component.fromString( inputCalendar.replace("\n", "\r\n")), onlyAfter=DateTime(2010, 1, 1, 0, 0, 0)) yield txn.commit() msg = email.message_from_string(self.sender.smtpSender.message) self.assertEquals(msg["From"], encodedFrom) self.assertEquals(self.inviteState, inviteState) self.assertEquals(self.orgEmail, outputOrganizerEmail) self.assertEquals(self.orgCn, outputOrganizerName) self.assertEquals(self.attendees, outputAttendeeList) self.assertEquals(self.fromAddress, outputFrom) self.assertEquals(self.toAddress, outputRecipient) self.assertEquals(msg["Subject"], outputSubject) if UID: # The organizer is local, and server is sending to remote # attendee txn = self.store.newTransaction() record = (yield txn.imipGetToken(inputOriginator, inputRecipient, UID)) yield txn.commit() self.assertNotEquals(record, None) self.assertEquals(msg["Reply-To"], "*****@*****.**" % (record.token, )) # Make sure attendee property for organizer exists and matches # the CUA of the organizer property orgValue = self.calendar.getOrganizerProperty().value() self.assertEquals( orgValue, self.calendar.getAttendeeProperty([orgValue]).value()) else: # Reply only -- the attendee is local, and server is sending reply to remote organizer self.assertEquals(msg["Reply-To"], self.fromAddress) # Check that we don't send any messages for events completely in # the past. self.sender.smtpSender.reset() txn = self.store.newTransaction() yield self.sender.outbound(txn, inputOriginator, inputRecipient, Component.fromString( inputCalendar.replace("\n", "\r\n")), onlyAfter=DateTime(2021, 1, 1, 0, 0, 0)) yield txn.commit() self.assertFalse(self.sender.smtpSender.sendMessageCalled)
def processReply(self, msg): # extract the token from the To header _ignore_name, addr = email.utils.parseaddr(msg['To']) if addr: # addr looks like: [email protected] token = self._extractToken(addr) if not token: log.error("Mail gateway didn't find a token in message " "%s (%s)" % (msg['Message-ID'], msg['To'])) returnValue(self.NO_TOKEN) else: log.error("Mail gateway couldn't parse To: address (%s) in " "message %s" % (msg['To'], msg['Message-ID'])) returnValue(self.MALFORMED_TO_ADDRESS) txn = self.store.newTransaction(label="MailReceiver.processReply") result = (yield txn.imipLookupByToken(token)) yield txn.commit() try: # Note the results are returned as utf-8 encoded strings organizer, attendee, _ignore_icaluid = result[0] except: # This isn't a token we recognize log.error("Mail gateway found a token (%s) but didn't " "recognize it in message %s" % (token, msg['Message-ID'])) returnValue(self.UNKNOWN_TOKEN) for part in msg.walk(): if part.get_content_type() == "text/calendar": calBody = part.get_payload(decode=True) break else: # No icalendar attachment log.warn("Mail gateway didn't find an icalendar attachment " "in message %s" % (msg['Message-ID'], )) toAddr = None fromAddr = attendee[7:] if organizer.startswith("mailto:"): toAddr = organizer[7:] elif organizer.startswith("urn:x-uid:"): uid = organizer[10:] record = yield self.directory.recordWithUID(uid) try: if record and record.emailAddresses: toAddr = list(record.emailAddresses)[0] except AttributeError: pass if toAddr is None: log.error("Don't have an email address for the organizer; " "ignoring reply.") returnValue(self.NO_ORGANIZER_ADDRESS) settings = config.Scheduling["iMIP"]["Sending"] smtpSender = SMTPSender(settings.Username, settings.Password, settings.UseSSL, settings.Server, settings.Port) del msg["From"] msg["From"] = fromAddr del msg["Reply-To"] msg["Reply-To"] = fromAddr del msg["To"] msg["To"] = toAddr log.warn("Mail gateway forwarding reply back to organizer") yield smtpSender.sendMessage(fromAddr, toAddr, messageid(), msg.as_string()) returnValue(self.REPLY_FORWARDED_TO_ORGANIZER) # Process the imip attachment; inject to calendar server log.debug(calBody) calendar = Component.fromString(calBody) event = calendar.mainComponent() # Don't let a missing PRODID prevent the reply from being processed if not calendar.hasProperty("PRODID"): calendar.addProperty(Property("PRODID", "Unknown")) calendar.removeAllButOneAttendee(attendee) organizerProperty = calendar.getOrganizerProperty() if organizerProperty is None: # ORGANIZER is required per rfc2446 section 3.2.3 log.warn("Mail gateway didn't find an ORGANIZER in REPLY %s" % (msg['Message-ID'], )) event.addProperty(Property("ORGANIZER", organizer)) else: organizerProperty.setValue(organizer) if not calendar.getAttendees(): # The attendee we're expecting isn't there, so add it back # with a SCHEDULE-STATUS of SERVICE_UNAVAILABLE. # The organizer will then see that the reply was not successful. attendeeProp = Property("ATTENDEE", attendee, params={ "SCHEDULE-STATUS": iTIPRequestStatus.SERVICE_UNAVAILABLE, }) event.addProperty(attendeeProp) # TODO: We have talked about sending an email to the reply-to # at this point, to let them know that their reply was missing # the appropriate ATTENDEE. This will require a new localizable # email template for the message. txn = self.store.newTransaction(label="MailReceiver.processReply") yield txn.enqueue(IMIPReplyWork, organizer=organizer, attendee=attendee, icalendarText=str(calendar)) yield txn.commit() returnValue(self.INJECTION_SUBMITTED)
def getComp(str): calendar = Component.fromString(str) comp = calendar.mainComponent() return comp
def test_index_revisions(self): data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """ data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=WEEKLY;COUNT=2 END:VEVENT END:VCALENDAR """ data3 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.3 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=WEEKLY;COUNT=2 END:VEVENT END:VCALENDAR """ calendar = Component.fromString(data1) self.db.addResource("data1.ics", calendar) calendar = Component.fromString(data2) self.db.addResource("data2.ics", calendar) calendar = Component.fromString(data3) self.db.addResource("data3.ics", calendar) self.db.deleteResource("data3.ics") tests = ( (0, ( [ "data1.ics", "data2.ics", ], [], [], )), (1, ( [ "data2.ics", ], [ "data3.ics", ], [], )), (2, ( [], [ "data3.ics", ], [], )), (3, ( [], [ "data3.ics", ], [], )), (4, ( [], [], [], )), (5, ( [], [], [], )), ) for revision, results in tests: self.assertEquals( self.db.whatchanged(revision), results, "Mismatched results for whatchanged with revision %d" % (revision, ))
def test_ImportComponentAttendee(self): # Have user02 invite this user01 yield storeComponentInHomeAndCalendar( self.store, Component.allFromString(DATA_USER02_INVITES_USER01_ORGANIZER_COPY), "user02", "calendar", "invite.ics") yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) # Delete the attendee's copy, thus declining the event txn = self.store.newTransaction() home = yield txn.calendarHomeWithUID("user01") collection = yield home.childWithName("calendar") objects = yield collection.objectResources() self.assertEquals(len(objects), 1) yield objects[0].remove() yield txn.commit() yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) # Make sure attendee's copy is gone txn = self.store.newTransaction() home = yield txn.calendarHomeWithUID("user01") collection = yield home.childWithName("calendar") objects = yield collection.objectResources() self.assertEquals(len(objects), 0) yield txn.commit() # Make sure attendee shows as declined to the organizer txn = self.store.newTransaction() home = yield txn.calendarHomeWithUID("user02") collection = yield home.childWithName("calendar") objects = yield collection.objectResources() self.assertEquals(len(objects), 1) component = yield objects[0].component() prop = component.getAttendeeProperty(("urn:x-uid:user01", )) self.assertEquals(prop.parameterValue("PARTSTAT"), "DECLINED") yield txn.commit() # When importing the event again, update through the organizer's copy # of the event as if it were an iTIP reply component = Component.allFromString( DATA_USER02_INVITES_USER01_ATTENDEE_COPY) yield importCollectionComponent(self.store, component) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) # Make sure organizer now sees the right partstats txn = self.store.newTransaction() home = yield txn.calendarHomeWithUID("user02") collection = yield home.childWithName("calendar") objects = yield collection.objectResources() self.assertEquals(len(objects), 1) component = yield objects[0].component() # print(str(component)) props = component.getAttendeeProperties(("urn:x-uid:user01", )) # The master is ACCEPTED self.assertEquals(props[0].parameterValue("PARTSTAT"), "ACCEPTED") # 2nd instance is TENTATIVE self.assertEquals(props[1].parameterValue("PARTSTAT"), "TENTATIVE") # 3rd instance is not in the attendee's copy, so remains DECLILNED self.assertEquals(props[2].parameterValue("PARTSTAT"), "DECLINED") yield txn.commit() # Make sure attendee now sees the right partstats txn = self.store.newTransaction() home = yield txn.calendarHomeWithUID("user01") collection = yield home.childWithName("calendar") objects = yield collection.objectResources() self.assertEquals(len(objects), 1) component = yield objects[0].component() # print(str(component)) props = component.getAttendeeProperties(("urn:x-uid:user01", )) # The master is ACCEPTED self.assertEquals(props[0].parameterValue("PARTSTAT"), "ACCEPTED") # 2nd instance is TENTATIVE self.assertEquals(props[1].parameterValue("PARTSTAT"), "TENTATIVE") # 3rd instance is not in the organizer's copy, so should inherit # the value from the master, which is ACCEPTED self.assertEquals(props[2].parameterValue("PARTSTAT"), "ACCEPTED") yield txn.commit()
def test_index_timespan(self): data = ( ( "#1.1 Simple component - busy", "1.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """, "20080601T000000Z", "20080602T000000Z", "mailto:[email protected]", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ), ), ( "#1.2 Simple component - transparent", "1.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.2 DTSTART:20080602T120000Z DTEND:20080602T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """, "20080602T000000Z", "20080603T000000Z", "mailto:[email protected]", (('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'T'), ), ), ( "#1.3 Simple component - canceled", "1.3", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.3 DTSTART:20080603T120000Z DTEND:20080603T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] STATUS:CANCELLED END:VEVENT END:VCALENDAR """, "20080603T000000Z", "20080604T000000Z", "mailto:[email protected]", (('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'F', 'F'), ), ), ( "#1.4 Simple component - tentative", "1.4", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.4 DTSTART:20080604T120000Z DTEND:20080604T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] STATUS:TENTATIVE END:VEVENT END:VCALENDAR """, "20080604T000000Z", "20080605T000000Z", "mailto:[email protected]", (('N', "2008-06-04 12:00:00", "2008-06-04 13:00:00", 'T', 'F'), ), ), ( "#2.1 Recurring component - busy", "2.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.1 DTSTART:20080605T120000Z DTEND:20080605T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=2 END:VEVENT END:VCALENDAR """, "20080605T000000Z", "20080607T000000Z", "mailto:[email protected]", ( ('N', "2008-06-05 12:00:00", "2008-06-05 13:00:00", 'B', 'F'), ('N', "2008-06-06 12:00:00", "2008-06-06 13:00:00", 'B', 'F'), ), ), ( "#2.2 Recurring component - busy", "2.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.2 DTSTART:20080607T120000Z DTEND:20080607T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=2 END:VEVENT BEGIN:VEVENT UID:12345-67890-2.2 RECURRENCE-ID:20080608T120000Z DTSTART:20080608T140000Z DTEND:20080608T150000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """, "20080607T000000Z", "20080609T000000Z", "mailto:[email protected]", ( ('N', "2008-06-07 12:00:00", "2008-06-07 13:00:00", 'B', 'F'), ('N', "2008-06-08 14:00:00", "2008-06-08 15:00:00", 'B', 'T'), ), ), ) for description, name, calendar_txt, trstart, trend, organizer, instances in data: calendar = Component.fromString(calendar_txt) with open(os.path.join(self.indexDirPath.path, name), "w") as f: f.write(calendar_txt) self.db.addResource(name, calendar) self.assertTrue(self.db.resourceExists(name), msg=description) # Create fake filter element to match time-range filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( TimeRange( start=trstart, end=trend, ), name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"), ), name="VCALENDAR", )) filter = Filter(filter) resources = yield self.db.indexedSearch(filter, fbtype=True) index_results = set() for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources: self.assertEqual(test_organizer, organizer, msg=description) index_results.add(( float, start, end, fbtype, transp, )) self.assertEqual(set(instances), index_results, msg=description)
class Inviter(BaseProfile): """ A Calendar user who invites other users to new events. """ _eventTemplate = Component.fromString("""\ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//iCal 4.0.3//EN CALSCALE:GREGORIAN BEGIN:VEVENT CREATED:20101018T155431Z UID:C98AD237-55AD-4F7D-9009-0D355D835822 DTEND;TZID=America/New_York:20101021T130000 TRANSP:OPAQUE SUMMARY:Simple event DTSTART;TZID=America/New_York:20101021T120000 DTSTAMP:20101018T155438Z SEQUENCE:2 END:VEVENT END:VCALENDAR """.replace("\n", "\r\n")) def setParameters( self, enabled=True, sendInvitationDistribution=NormalDistribution(600, 60), inviteeDistribution=UniformDiscreteDistribution(range(-10, 11)), inviteeClumping=True, inviteeCountDistribution=LogNormalDistribution(1.2, 1.2), eventStartDistribution=NearFutureDistribution(), eventDurationDistribution=UniformDiscreteDistribution( [15 * 60, 30 * 60, 45 * 60, 60 * 60, 120 * 60]), recurrenceDistribution=RecurrenceDistribution(False), ): self.enabled = enabled self._sendInvitationDistribution = sendInvitationDistribution self._inviteeDistribution = inviteeDistribution self._inviteeClumping = inviteeClumping self._inviteeCountDistribution = inviteeCountDistribution self._eventStartDistribution = eventStartDistribution self._eventDurationDistribution = eventDurationDistribution self._recurrenceDistribution = recurrenceDistribution def run(self): return loopWithDistribution(self._reactor, self._sendInvitationDistribution, self._invite) def _addAttendee(self, event, attendees): """ Create a new attendee to add to the list of attendees for the given event. """ selfRecord = self._sim.getUserRecord(self._number) invitees = set([u'mailto:%s' % (selfRecord.email, )]) for att in attendees: invitees.add(att.value()) for _ignore_i in range(10): sample = self._inviteeDistribution.sample() if self._inviteeClumping: sample = self._number + sample invitee = max(0, sample) try: record = self._sim.getUserRecord(invitee) except IndexError: continue cuaddr = u'mailto:%s' % (record.email, ) if cuaddr not in invitees: break else: raise CannotAddAttendee("Can't find uninvited user to invite.") attendee = Property( name=u'ATTENDEE', value=cuaddr.encode("utf-8"), params={ 'CN': record.commonName, 'CUTYPE': 'INDIVIDUAL', 'PARTSTAT': 'NEEDS-ACTION', 'ROLE': 'REQ-PARTICIPANT', 'RSVP': 'TRUE', }, ) event.addProperty(attendee) attendees.append(attendee) def _invite(self): """ Try to add a new event, or perhaps remove an existing attendee from an event. @return: C{None} if there are no events to play with, otherwise a L{Deferred} which fires when the attendee change has been made. """ if not self._client.started: return succeed(None) # Find calendars which are eligible for invites calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT") while calendars: # Pick one at random from which to try to create an event # to modify. calendar = self.random.choice(calendars) calendars.remove(calendar) # Copy the template event and fill in some of its fields # to make a new event to create on the calendar. vcalendar = self._eventTemplate.duplicate() vevent = vcalendar.mainComponent() uid = str(uuid4()) dtstart = self._eventStartDistribution.sample() dtend = dtstart + Duration( seconds=self._eventDurationDistribution.sample()) vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC())) vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC())) vevent.replaceProperty(Property("DTSTART", dtstart)) vevent.replaceProperty(Property("DTEND", dtend)) vevent.replaceProperty(Property("UID", uid)) rrule = self._recurrenceDistribution.sample() if rrule is not None: vevent.addProperty(Property(None, None, None, pycalendar=rrule)) vevent.addProperty(self._client._makeSelfOrganizer()) vevent.addProperty(self._client._makeSelfAttendee()) attendees = list(vevent.properties('ATTENDEE')) for _ignore in range(int(self._inviteeCountDistribution.sample())): try: self._addAttendee(vevent, attendees) except CannotAddAttendee: continue href = '%s%s.ics' % (calendar.url, uid) d = self._client.addInvite(href, vcalendar) return self._newOperation("invite", d)
class HomeMigrationTests(CommonCommonTests, TestCase): """ Tests for L{UpgradeToDatabaseStep}. """ av1 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN BEGIN:VAVAILABILITY ORGANIZER:mailto:[email protected] UID:[email protected] DTSTAMP:20061005T133225Z DTEND:20140101T000000Z BEGIN:AVAILABLE UID:[email protected] DTSTAMP:20061005T133225Z SUMMARY:Monday to Friday from 9:00 to 17:00 DTSTART:20130101T090000Z DTEND:20130101T170000Z RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR END:AVAILABLE END:VAVAILABILITY END:VCALENDAR """) @inlineCallbacks def setUp(self): """ Set up two stores to migrate between. """ yield super(HomeMigrationTests, self).setUp() yield self.buildStoreAndDirectory(extraUids=( u"home1", u"home2", u"home3", u"home_defaults", u"home_no_splits", u"home_splits", u"home_splits_shared", )) self.sqlStore = self.store # Add some files to the file store. self.filesPath = CachingFilePath(self.mktemp()) self.filesPath.createDirectory() fileStore = self.fileStore = CommonDataStore( self.filesPath, {"push": StubNotifierFactory()}, self.directory, True, True) self.upgrader = UpgradeToDatabaseStep(self.fileStore, self.sqlStore) requirements = CommonTests.requirements extras = deriveValue(self, "extraRequirements", lambda t: {}) requirements = self.mergeRequirements(requirements, extras) yield populateCalendarsFrom(requirements, fileStore) md5s = CommonTests.md5s yield resetCalendarMD5s(md5s, fileStore) self.filesPath.child("calendars").child("__uids__").child("ho").child( "me").child("home1").child(".some-extra-data").setContent( "some extra data") requirements = ABCommonTests.requirements yield populateAddressBooksFrom(requirements, fileStore) md5s = ABCommonTests.md5s yield resetAddressBookMD5s(md5s, fileStore) self.filesPath.child("addressbooks").child("__uids__").child( "ho").child("me").child("home1").child( ".some-extra-data").setContent("some extra data") # Add some properties we want to check get migrated over txn = self.fileStore.newTransaction() home = yield txn.calendarHomeWithUID("home_defaults") cal = yield home.calendarWithName("calendar_1") props = cal.properties() props[PropertyName.fromElement( caldavxml.SupportedCalendarComponentSet )] = caldavxml.SupportedCalendarComponentSet( caldavxml.CalendarComponent(name="VEVENT"), caldavxml.CalendarComponent(name="VTODO"), ) props[PropertyName.fromElement( element.ResourceType)] = element.ResourceType( element.Collection(), caldavxml.Calendar(), ) props[PropertyName.fromElement( customxml.GETCTag)] = customxml.GETCTag.fromString("foobar") inbox = yield home.calendarWithName("inbox") props = inbox.properties() props[PropertyName.fromElement( customxml.CalendarAvailability )] = customxml.CalendarAvailability.fromString(str(self.av1)) props[PropertyName.fromElement( caldavxml.ScheduleDefaultCalendarURL )] = caldavxml.ScheduleDefaultCalendarURL( element.HRef.fromString( "/calendars/__uids__/home_defaults/calendar_1"), ) yield txn.commit() def mergeRequirements(self, a, b): """ Merge two requirements dictionaries together, modifying C{a} and returning it. @param a: Some requirements, in the format of L{CommonTests.requirements}. @type a: C{dict} @param b: Some additional requirements, to be merged into C{a}. @type b: C{dict} @return: C{a} @rtype: C{dict} """ for homeUID in b: homereq = a.setdefault(homeUID, {}) homeExtras = b[homeUID] for calendarUID in homeExtras: calreq = homereq.setdefault(calendarUID, {}) calendarExtras = homeExtras[calendarUID] calreq.update(calendarExtras) return a @withSpecialValue( "extraRequirements", { "home1": { "calendar_1": { "bogus.ics": (getModule("twistedcaldav").filePath.sibling("zoneinfo"). child("EST.ics").getContent(), CommonTests.metadata1) } } }) @inlineCallbacks def test_unknownTypeNotMigrated(self): """ The only types of calendar objects that should get migrated are VEVENTs and VTODOs. Other component types, such as free-standing VTIMEZONEs, don't have a UID and can't be stored properly in the database, so they should not be migrated. """ yield self.upgrader.stepWithResult(None) txn = self.sqlStore.newTransaction() self.addCleanup(txn.commit) self.assertIdentical( None, (yield (yield (yield (yield txn.calendarHomeWithUID("home1")).calendarWithName( "calendar_1"))).calendarObjectWithName("bogus.ics"))) @inlineCallbacks def test_upgradeCalendarHomes(self): """ L{UpgradeToDatabaseService.startService} will do the upgrade, then start its dependent service by adding it to its service hierarchy. """ # Create a fake directory in the same place as a home, but with a non-existent uid fake_dir = self.filesPath.child("calendars").child("__uids__").child( "ho").child("me").child("foobar") fake_dir.makedirs() # Create a fake file in the same place as a home,with a name that matches the hash uid prefix fake_file = self.filesPath.child("calendars").child("__uids__").child( "ho").child("me").child("home_file") fake_file.setContent("") yield self.upgrader.stepWithResult(None) txn = self.sqlStore.newTransaction() self.addCleanup(txn.commit) for uid in CommonTests.requirements: if CommonTests.requirements[uid] is not None: self.assertNotIdentical(None, (yield txn.calendarHomeWithUID(uid))) # Successfully migrated calendar homes are deleted self.assertFalse( self.filesPath.child("calendars").child("__uids__").child( "ho").child("me").child("home1").exists()) # Want metadata preserved home = (yield txn.calendarHomeWithUID("home1")) calendar = (yield home.calendarWithName("calendar_1")) for name, metadata, md5 in ( ("1.ics", CommonTests.metadata1, CommonTests.md5Values[0]), ("2.ics", CommonTests.metadata2, CommonTests.md5Values[1]), ("3.ics", CommonTests.metadata3, CommonTests.md5Values[2]), ): object = (yield calendar.calendarObjectWithName(name)) self.assertEquals(object.getMetadata(), metadata) self.assertEquals(object.md5(), md5) @withSpecialValue("extraRequirements", {"nonexistent": {"calendar_1": {}}}) @inlineCallbacks def test_upgradeCalendarHomesMissingDirectoryRecord(self): """ Test an upgrade where a directory record is missing for a home; the original home directory will remain on disk. """ yield self.upgrader.stepWithResult(None) txn = self.sqlStore.newTransaction() self.addCleanup(txn.commit) for uid in CommonTests.requirements: if CommonTests.requirements[uid] is not None: self.assertNotIdentical(None, (yield txn.calendarHomeWithUID(uid))) self.assertIdentical(None, (yield txn.calendarHomeWithUID(u"nonexistent"))) # Skipped calendar homes are not deleted self.assertTrue( self.filesPath.child("calendars").child("__uids__").child( "no").child("ne").child("nonexistent").exists()) @inlineCallbacks def test_upgradeExistingHome(self): """ L{UpgradeToDatabaseService.startService} will skip migrating existing homes. """ startTxn = self.sqlStore.newTransaction("populate empty sample") yield startTxn.calendarHomeWithUID("home1", create=True) yield startTxn.commit() yield self.upgrader.stepWithResult(None) vrfyTxn = self.sqlStore.newTransaction("verify sample still empty") self.addCleanup(vrfyTxn.commit) home = yield vrfyTxn.calendarHomeWithUID("home1") # The default calendar is still there. self.assertNotIdentical(None, (yield home.calendarWithName("calendar"))) # The migrated calendar isn't. self.assertIdentical(None, (yield home.calendarWithName("calendar_1"))) @inlineCallbacks def test_upgradeAttachments(self): """ L{UpgradeToDatabaseService.startService} upgrades calendar attachments as well. """ # Need to tweak config and settings to setup dropbox to work self.patch(config, "EnableDropBox", True) self.patch(config, "EnableManagedAttachments", False) self.sqlStore.enableManagedAttachments = False txn = self.sqlStore.newTransaction() cs = schema.CALENDARSERVER yield Delete(From=cs, Where=cs.NAME == "MANAGED-ATTACHMENTS").on(txn) yield txn.commit() txn = self.fileStore.newTransaction() committed = [] def maybeCommit(): if not committed: committed.append(True) return txn.commit() self.addCleanup(maybeCommit) @inlineCallbacks def getSampleObj(): home = (yield txn.calendarHomeWithUID("home1")) calendar = (yield home.calendarWithName("calendar_1")) object = (yield calendar.calendarObjectWithName("1.ics")) returnValue(object) inObject = yield getSampleObj() someAttachmentName = "some-attachment" someAttachmentType = MimeType.fromString("application/x-custom-type") attachment = yield inObject.createAttachmentWithName( someAttachmentName, ) transport = attachment.store(someAttachmentType) someAttachmentData = "Here is some data for your attachment, enjoy." transport.write(someAttachmentData) yield transport.loseConnection() yield maybeCommit() yield self.upgrader.stepWithResult(None) committed = [] txn = self.sqlStore.newTransaction() outObject = yield getSampleObj() outAttachment = yield outObject.attachmentWithName(someAttachmentName) allDone = Deferred() class SimpleProto(Protocol): data = '' def dataReceived(self, data): self.data += data def connectionLost(self, reason): allDone.callback(self.data) self.assertEquals(outAttachment.contentType(), someAttachmentType) outAttachment.retrieve(SimpleProto()) allData = yield allDone self.assertEquals(allData, someAttachmentData) @inlineCallbacks def test_upgradeAddressBookHomes(self): """ L{UpgradeToDatabaseService.startService} will do the upgrade, then start its dependent service by adding it to its service hierarchy. """ yield self.upgrader.stepWithResult(None) txn = self.sqlStore.newTransaction() self.addCleanup(txn.commit) for uid in ABCommonTests.requirements: if ABCommonTests.requirements[uid] is not None: self.assertNotIdentical( None, (yield txn.addressbookHomeWithUID(uid))) # Successfully migrated addressbook homes are deleted self.assertFalse( self.filesPath.child("addressbooks").child("__uids__").child( "ho").child("me").child("home1").exists()) # Want metadata preserved home = (yield txn.addressbookHomeWithUID("home1")) adbk = (yield home.addressbookWithName("addressbook")) for name, md5 in ( ("1.vcf", ABCommonTests.md5Values[0]), ("2.vcf", ABCommonTests.md5Values[1]), ("3.vcf", ABCommonTests.md5Values[2]), ): object = (yield adbk.addressbookObjectWithName(name)) self.assertEquals(object.md5(), md5) @inlineCallbacks def test_upgradeProperties(self): """ L{UpgradeToDatabaseService.startService} will do the upgrade, then start its dependent service by adding it to its service hierarchy. """ yield self.upgrader.stepWithResult(None) txn = self.sqlStore.newTransaction() self.addCleanup(txn.commit) # Want metadata preserved home = (yield txn.calendarHomeWithUID("home_defaults")) cal = (yield home.calendarWithName("calendar_1")) inbox = (yield home.calendarWithName("inbox")) # Supported components self.assertEqual(cal.getSupportedComponents(), "VEVENT") self.assertTrue(cal.properties().get( PropertyName.fromElement(caldavxml.SupportedCalendarComponentSet)) is None) # Resource type removed self.assertTrue(cal.properties().get( PropertyName.fromElement(element.ResourceType)) is None) # Ctag removed self.assertTrue(cal.properties().get( PropertyName.fromElement(customxml.GETCTag)) is None) # Availability self.assertEquals(str(home.getAvailability()), str(self.av1)) self.assertTrue(inbox.properties().get( PropertyName.fromElement(customxml.CalendarAvailability)) is None) # Default calendar self.assertTrue(home.isDefaultCalendar(cal)) self.assertTrue(inbox.properties().get( PropertyName.fromElement(caldavxml.ScheduleDefaultCalendarURL)) is None) def test_fileStoreFromPath(self): """ Verify that fileStoreFromPath() will return a CommonDataStore if the given path contains either "calendars" or "addressbooks" sub-directories. Otherwise it returns None """ # No child directories docRootPath = CachingFilePath(self.mktemp()) docRootPath.createDirectory() step = UpgradeToDatabaseStep.fileStoreFromPath(docRootPath) self.assertEquals(step, None) # "calendars" child directory exists childPath = docRootPath.child("calendars") childPath.createDirectory() step = UpgradeToDatabaseStep.fileStoreFromPath(docRootPath) self.assertTrue(isinstance(step, CommonDataStore)) childPath.remove() # "addressbooks" child directory exists childPath = docRootPath.child("addressbooks") childPath.createDirectory() step = UpgradeToDatabaseStep.fileStoreFromPath(docRootPath) self.assertTrue(isinstance(step, CommonDataStore)) childPath.remove()
def test_index(self): data = ( ( "#1.1 Simple component", "1.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """, False, True, ), ( "#2.1 Recurring component", "2.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=WEEKLY;COUNT=2 END:VEVENT END:VCALENDAR """, False, True, ), ( "#2.2 Recurring component with override", "2.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.2 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=WEEKLY;COUNT=2 END:VEVENT BEGIN:VEVENT UID:12345-67890-2.2 RECURRENCE-ID:20080608T120000Z DTSTART:20080608T120000Z DTEND:20080608T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """, False, True, ), ( "#2.3 Recurring component with broken override - new", "2.3", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.3 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=WEEKLY;COUNT=2 END:VEVENT BEGIN:VEVENT UID:12345-67890-2.3 RECURRENCE-ID:20080609T120000Z DTSTART:20080608T120000Z DTEND:20080608T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """, False, False, ), ( "#2.4 Recurring component with broken override - existing", "2.4", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-2.4 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=WEEKLY;COUNT=2 END:VEVENT BEGIN:VEVENT UID:12345-67890-2.4 RECURRENCE-ID:20080609T120000Z DTSTART:20080608T120000Z DTEND:20080608T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """, True, True, ), ) for description, name, calendar_txt, reCreate, ok in data: calendar = Component.fromString(calendar_txt) if ok: with open(os.path.join(self.indexDirPath.path, name), "w") as f: f.write(calendar_txt) self.db.addResource(name, calendar, reCreate=reCreate) self.assertTrue(self.db.resourceExists(name), msg=description) else: self.assertRaises(InvalidOverriddenInstanceError, self.db.addResource, name, calendar) self.assertFalse(self.db.resourceExists(name), msg=description) self.db._db_recreate() for description, name, calendar_txt, reCreate, ok in data: if ok: self.assertTrue(self.db.resourceExists(name), msg=description) else: self.assertFalse(self.db.resourceExists(name), msg=description) self.db.testAndUpdateIndex(DateTime(2020, 1, 1)) for description, name, calendar_txt, reCreate, ok in data: if ok: self.assertTrue(self.db.resourceExists(name), msg=description) else: self.assertFalse(self.db.resourceExists(name), msg=description)
class Eventer(BaseProfile): """ A Calendar user who creates new events. """ _eventTemplate = Component.fromString("""\ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//iCal 4.0.3//EN CALSCALE:GREGORIAN BEGIN:VEVENT CREATED:20101018T155431Z UID:C98AD237-55AD-4F7D-9009-0D355D835822 DTEND;TZID=America/New_York:20101021T130000 TRANSP:OPAQUE SUMMARY:Simple event DTSTART;TZID=America/New_York:20101021T120000 DTSTAMP:20101018T155438Z SEQUENCE:2 END:VEVENT END:VCALENDAR """.replace("\n", "\r\n")) def setParameters( self, enabled=True, interval=25, eventStartDistribution=NearFutureDistribution(), eventDurationDistribution=UniformDiscreteDistribution( [15 * 60, 30 * 60, 45 * 60, 60 * 60, 120 * 60]), recurrenceDistribution=RecurrenceDistribution(False), ): self.enabled = enabled self._interval = interval self._eventStartDistribution = eventStartDistribution self._eventDurationDistribution = eventDurationDistribution self._recurrenceDistribution = recurrenceDistribution def run(self): self._call = LoopingCall(self._addEvent) self._call.clock = self._reactor return self._call.start(self._interval) def _addEvent(self): # Don't perform any operations until the client is up and running if not self._client.started: return succeed(None) calendar = self._getRandomCalendarOfType('VEVENT') if not calendar: # No VEVENT calendars, so no new event... return succeed(None) # Copy the template event and fill in some of its fields # to make a new event to create on the calendar. vcalendar = self._eventTemplate.duplicate() vevent = vcalendar.mainComponent() uid = str(uuid4()) dtstart = self._eventStartDistribution.sample() dtend = dtstart + Duration( seconds=self._eventDurationDistribution.sample()) vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC())) vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC())) vevent.replaceProperty(Property("DTSTART", dtstart)) vevent.replaceProperty(Property("DTEND", dtend)) vevent.replaceProperty(Property("UID", uid)) rrule = self._recurrenceDistribution.sample() if rrule is not None: vevent.addProperty(Property(None, None, None, pycalendar=rrule)) href = '%s%s.ics' % (calendar.url, uid) d = self._client.addEvent(href, vcalendar) return self._newOperation("create", d)
def test_index_timespan_per_user(self): data = ( ( "#1.1 Single per-user non-recurring component", "1.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.1 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080602T000000Z", "mailto:[email protected]", ( ( "user01", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ), ), ( "user02", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ), ), ), ), ( "#1.2 Two per-user non-recurring component", "1.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.2 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user02 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080602T000000Z", "mailto:[email protected]", ( ( "user01", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ), ), ( "user02", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ), ), ( "user03", (('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ), ), ), ), ( "#2.1 Single per-user simple recurring component", "2.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=10 END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.1 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080603T000000Z", "mailto:[email protected]", ( ( "user01", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'T'), ), ), ( "user02", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'F'), ), ), ), ), ( "#2.2 Two per-user simple recurring component", "2.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.2 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=10 END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user02 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080603T000000Z", "mailto:[email protected]", ( ( "user01", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'T'), ), ), ( "user02", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'F'), ), ), ( "user03", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 12:00:00", "2008-06-02 13:00:00", 'B', 'F'), ), ), ), ), ( "#3.1 Single per-user complex recurring component", "3.1", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.1 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=10 END:VEVENT BEGIN:VEVENT UID:12345-67890-1.1 RECURRENCE-ID:20080602T120000Z DTSTART:20080602T130000Z DTEND:20080602T140000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.1 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE BEGIN:X-CALENDARSERVER-PERINSTANCE RECURRENCE-ID:20080602T120000Z TRANSP:OPAQUE END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080604T000000Z", "mailto:[email protected]", ( ( "user01", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'T'), ), ), ( "user02", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'F'), ), ), ), ), ( "#3.2 Two per-user complex recurring component", "3.2", """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-1.2 DTSTART:20080601T120000Z DTEND:20080601T130000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] RRULE:FREQ=DAILY;COUNT=10 END:VEVENT BEGIN:VEVENT UID:12345-67890-1.2 RECURRENCE-ID:20080602T120000Z DTSTART:20080602T130000Z DTEND:20080602T140000Z DTSTAMP:20080601T120000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user01 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE BEGIN:X-CALENDARSERVER-PERINSTANCE RECURRENCE-ID:20080602T120000Z TRANSP:OPAQUE END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER BEGIN:X-CALENDARSERVER-PERUSER UID:12345-67890-1.2 X-CALENDARSERVER-PERUSER-UID:user02 BEGIN:X-CALENDARSERVER-PERINSTANCE BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:Test TRIGGER;RELATED=START:-PT10M END:VALARM END:X-CALENDARSERVER-PERINSTANCE BEGIN:X-CALENDARSERVER-PERINSTANCE RECURRENCE-ID:20080603T120000Z TRANSP:TRANSPARENT END:X-CALENDARSERVER-PERINSTANCE END:X-CALENDARSERVER-PERUSER END:VCALENDAR """, "20080601T000000Z", "20080604T000000Z", "mailto:[email protected]", ( ( "user01", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'T'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'T'), ), ), ( "user02", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'T'), ), ), ( "user03", ( ('N', "2008-06-01 12:00:00", "2008-06-01 13:00:00", 'B', 'F'), ('N', "2008-06-02 13:00:00", "2008-06-02 14:00:00", 'B', 'F'), ('N', "2008-06-03 12:00:00", "2008-06-03 13:00:00", 'B', 'F'), ), ), ), ), ) for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data: calendar = Component.fromString(calendar_txt) with open(os.path.join(self.indexDirPath.path, name), "w") as f: f.write(calendar_txt) self.db.addResource(name, calendar) self.assertTrue(self.db.resourceExists(name), msg=description) # Create fake filter element to match time-range filter = caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( TimeRange( start=trstart, end=trend, ), name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"), ), name="VCALENDAR", )) filter = Filter(filter) for useruid, instances in peruserinstances: resources = yield self.db.indexedSearch(filter, useruid=useruid, fbtype=True) index_results = set() for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources: self.assertEqual(test_organizer, organizer, msg=description) index_results.add(( str(float), str(start), str(end), str(fbtype), str(transp), )) self.assertEqual(set(instances), index_results, msg="%s, user:%s" % ( description, useruid, )) self.db.deleteResource(name)
class AlarmAcknowledger(BaseProfile): """ A Calendar user who creates a new event, and then updates its alarm. """ _eventTemplate = Component.fromString("""\ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//iCal 4.0.3//EN CALSCALE:GREGORIAN BEGIN:VEVENT CREATED:20101018T155431Z UID:C98AD237-55AD-4F7D-9009-0D355D835822 DTEND;TZID=America/New_York:20101021T130000 TRANSP:OPAQUE SUMMARY:Simple event DTSTART;TZID=America/New_York:20101021T120000 DTSTAMP:20101018T155438Z SEQUENCE:2 BEGIN:VALARM X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF DESCRIPTION:Event reminder TRIGGER:-PT8M ACTION:DISPLAY END:VALARM END:VEVENT END:VCALENDAR """.replace("\n", "\r\n")) def setParameters( self, enabled=True, interval=5, pastTheHour=[0, 15, 30, 45], eventStartDistribution=NearFutureDistribution(), eventDurationDistribution=UniformDiscreteDistribution( [15 * 60, 30 * 60, 45 * 60, 60 * 60, 120 * 60]), recurrenceDistribution=RecurrenceDistribution(False), ): self.enabled = enabled self._interval = interval self._pastTheHour = pastTheHour self._eventStartDistribution = eventStartDistribution self._eventDurationDistribution = eventDurationDistribution self._recurrenceDistribution = recurrenceDistribution self._lastMinuteChecked = -1 def initialize(self): """ Called before the profile runs for real. Can be used to initialize client state. @return: a L{Deferred} that fires when initialization is done """ return self._initEvent() def run(self): self._call = LoopingCall(self._updateEvent) self._call.clock = self._reactor return self._call.start(self._interval) def _initEvent(self): # Don't perform any operations until the client is up and running if not self._client.started: return succeed(None) # If it already exists, don't re-create calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0] if calendar.events: events = [ event for event in calendar.events.values() if event.url.endswith("event_to_update.ics") ] if events: return succeed(None) # Copy the template event and fill in some of its fields # to make a new event to create on the calendar. vcalendar = self._eventTemplate.duplicate() vevent = vcalendar.mainComponent() uid = str(uuid4()) dtstart = self._eventStartDistribution.sample() dtend = dtstart + Duration( seconds=self._eventDurationDistribution.sample()) vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC())) vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC())) vevent.replaceProperty(Property("DTSTART", dtstart)) vevent.replaceProperty(Property("DTEND", dtend)) vevent.replaceProperty(Property("UID", uid)) rrule = self._recurrenceDistribution.sample() if rrule is not None: vevent.addProperty(Property(None, None, None, pycalendar=rrule)) href = '%s%s' % (calendar.url, "event_to_update.ics") d = self._client.addEvent(href, vcalendar) return self._newOperation("create", d) def _shouldUpdate(self, minutePastTheHour): """ We want to only acknowledge our alarm at the "past the hour" minutes we've been configured for. """ should = False if minutePastTheHour in self._pastTheHour: # This is one of the minutes we should update on, but only update # as we pass into this minute, and not subsequent times if minutePastTheHour != self._lastMinuteChecked: should = True self._lastMinuteChecked = minutePastTheHour return should def _updateEvent(self): """ Set the ACKNOWLEDGED property on an event. @return: C{None} if there are no events to play with, otherwise a L{Deferred} which fires when the acknowledged change has been made. """ # Only do updates when we reach of the designated minutes past the hour if not self._shouldUpdate(datetime.now().minute): return succeed(None) if not self._client.started: return succeed(None) # If it does not exist, try to create it calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0] if not calendar.events: return self._initEvent() events = [ event for event in calendar.events.values() if event.url.endswith("event_to_update.ics") ] if not events: return self._initEvent() event = events[0] # Add/update the ACKNOWLEDGED property component = event.component.mainComponent() component.replaceProperty( Property("ACKNOWLEDGED", DateTime.getNowUTC())) d = self._client.changeEvent(event.url) return self._newOperation("update", d)
def buildFreeBusyResult(fbinfo, timerange, organizer=None, attendee=None, uid=None, method=None, event_details=None): """ Generate a VCALENDAR object containing a single VFREEBUSY that is the aggregate of the free busy info passed in. @param fbinfo: the array of busy periods to use. @param timerange: the L{TimeRange} for the query. @param organizer: the L{Property} for the Organizer of the free busy request, or None. @param attendee: the L{Property} for the Attendee responding to the free busy request, or None. @param uid: the UID value from the free busy request. @param method: the METHOD property value to insert. @param event_details: VEVENT components to add. @return: the L{Component} containing the calendar data. """ # Merge overlapping time ranges in each fb info section normalizePeriodList(fbinfo[0]) normalizePeriodList(fbinfo[1]) normalizePeriodList(fbinfo[2]) # Now build a new calendar object with the free busy info we have fbcalendar = Component("VCALENDAR") fbcalendar.addProperty(Property("VERSION", "2.0")) fbcalendar.addProperty(Property("PRODID", iCalendarProductID)) if method: fbcalendar.addProperty(Property("METHOD", method)) fb = Component("VFREEBUSY") fbcalendar.addComponent(fb) if organizer is not None: fb.addProperty(organizer) if attendee is not None: fb.addProperty(attendee) fb.addProperty(Property("DTSTART", timerange.start)) fb.addProperty(Property("DTEND", timerange.end)) fb.addProperty(Property("DTSTAMP", DateTime.getNowUTC())) if len(fbinfo[0]) != 0: fb.addProperty(Property("FREEBUSY", fbinfo[0], {"FBTYPE": "BUSY"})) if len(fbinfo[1]) != 0: fb.addProperty( Property("FREEBUSY", fbinfo[1], {"FBTYPE": "BUSY-TENTATIVE"})) if len(fbinfo[2]) != 0: fb.addProperty( Property("FREEBUSY", fbinfo[2], {"FBTYPE": "BUSY-UNAVAILABLE"})) if uid is not None: fb.addProperty(Property("UID", uid)) else: uid = str(uuid.uuid4()) fb.addProperty(Property("UID", uid)) if event_details: for vevent in event_details: fbcalendar.addComponent(vevent) return fbcalendar
def __init__(self, data): self.ical = Component.fromString(data)
def test_validation_processScheduleTags(self): """ Test that schedule tags are correctly updated. """ data1 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT END:VCALENDAR """ calendar_collection = (yield self.calendarUnderTest(home="user01")) calendar = Component.fromString(data1) yield calendar_collection.createCalendarObjectWithName( "test.ics", calendar) yield self.commit() data2 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] SUMMARY:Changed #1 END:VEVENT END:VCALENDAR """ calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data2) yield calendar_resource.setComponent(calendar) schedule_tag = calendar_resource.scheduleTag yield self.commit() data3 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] SUMMARY:Changed #2 END:VEVENT END:VCALENDAR """ calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data3) yield calendar_resource.setComponent(calendar) self.assertNotEqual(calendar_resource.scheduleTag, schedule_tag) schedule_tag = calendar_resource.scheduleTag yield self.commit() data4 = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890-attendee-reply DTSTAMP:20080601T120000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE;PARTSTAT=ACCEPTED:mailto:[email protected] SUMMARY:Changed #2 END:VEVENT END:VCALENDAR """ calendar_resource = (yield self.calendarObjectUnderTest( name="test.ics", home="user01", )) calendar = Component.fromString(data4) yield calendar_resource._setComponentInternal( calendar, internal_state=ComponentUpdateState.ORGANIZER_ITIP_UPDATE) self.assertEqual(calendar_resource.scheduleTag, schedule_tag) yield self.commit()
def test_connectionRefusedForAttendee(self): data_organizer = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:uid_data DTSTART:{now1:04d}0102T160000Z DURATION:PT1H CREATED:20060102T190000Z DTSTAMP:20051222T210507Z SUMMARY:data01_2 ORGANIZER:mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """.replace("\n", "\r\n").format(**self.now) data_attendee = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:uid_data DTSTART:{now1:04d}0102T160000Z DURATION:PT1H CREATED:20060102T190000Z DTSTAMP:20051222T210507Z SUMMARY:data01_2 ORGANIZER:mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE;PARTSTAT=DECLINED:mailto:[email protected] END:VEVENT END:VCALENDAR """.replace("\n", "\r\n").format(**self.now) # Organizer schedules home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True) calendar = yield home.childWithName("calendar") yield calendar.createCalendarObjectWithName("1.ics", Component.fromString(data_organizer)) yield self.commitTransaction(0) yield self.waitAllEmpty() # Data for user02 home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user02", create=True) calendar = yield home.childWithName("calendar") cobjs = yield calendar.calendarObjects() self.assertEqual(len(cobjs), 1) self.assertEqual(cobjs[0].uid(), "uid_data") yield self.commitTransaction(0) # Data for puser02 home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1), name="puser02", create=True) calendar = yield home.childWithName("calendar") cobjs = yield calendar.calendarObjects() self.assertEqual(len(cobjs), 1) self.assertEqual(cobjs[0].uid(), "uid_data") yield self.commitTransaction(1) # Stop cross-pod connection from working self.refuseConnection = True # Attendee changes home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1), name="puser02", create=True) calendar = yield home.childWithName("calendar") cobjs = yield calendar.calendarObjects() yield cobjs[0].setComponent(Component.fromString(data_attendee)) yield self.commitTransaction(1) while True: jobs = yield JobItem.all(self.theTransactionUnderTest(1)) yield self.commitTransaction(1) if len(jobs) == 1 and jobs[0].failed > 0: break # Organizer data unchanged cobj = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics") comp = yield cobj.componentForUser() self.assertTrue("DECLINED" not in str(comp)) yield self.commitTransaction(0) # Now allow cross-pod to work self.refuseConnection = False yield self.waitAllEmpty() # Organizer data changed cobj = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(0), home="user01", calendar_name="calendar", name="1.ics") comp = yield cobj.componentForUser() self.assertTrue("DECLINED" in str(comp)) yield self.commitTransaction(0)