def test_notificationObjectRevisions(self): """ Verify that all extra notification object revisions are deleted by FindMinValidRevisionWork and RevisionCleanupWork """ # get sync token home = yield self.homeUnderTest(name="user01") token = yield home.syncToken() #make notification changes as side effect of sharing yield self._createCalendarShare() # Get object revisions rev = schema.NOTIFICATION_OBJECT_REVISIONS revisionRows = yield Select( [rev.REVISION], From=rev, ).on(self.transactionUnderTest()) self.assertNotEqual(len(revisionRows), 0) # do FindMinValidRevisionWork yield self.transactionUnderTest().enqueue( FindMinValidRevisionWork, notBefore=datetime.datetime.utcnow()) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) # Get the minimum valid revision and check it minValidRevision = yield self.transactionUnderTest( ).calendarserverValue("MIN-VALID-REVISION") self.assertEqual(int(minValidRevision), max([row[0] for row in revisionRows])) # do RevisionCleanupWork yield self.transactionUnderTest().enqueue( RevisionCleanupWork, notBefore=datetime.datetime.utcnow()) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) # Get group1 object revision rev = schema.NOTIFICATION_OBJECT_REVISIONS revisionRows = yield Select( [rev.REVISION], From=rev, ).on(self.transactionUnderTest()) self.assertEqual(len(revisionRows), 1) # deleteRevisionsBefore() leaves 1 revision behind # old sync token fails home = yield self.homeUnderTest(name="user01") self.failUnlessFailure(home.resourceNamesSinceToken(token, "1"), SyncTokenValidException) self.failUnlessFailure(home.resourceNamesSinceToken(token, "infinity"), SyncTokenValidException)
def _runAllJobs(self): """ Run all outstanding jobs. """ # Run jobs jobs = yield JobItem.all(self.transactionUnderTest()) while jobs: yield jobs[0].run() yield self.commit() jobs = yield JobItem.all(self.transactionUnderTest()) yield self.commit()
def action_refreshgroups(self, j): txn = self._store.newTransaction() yield txn.directoryService().flush() wp = yield GroupCacherPollingWork.reschedule(txn, 0, force=True) jobID = wp.workItem.jobID yield txn.commit() if "wait" in j and j["wait"]: yield JobItem.waitJobDone(self._store.newTransaction, reactor, 60.0, jobID) yield JobItem.waitWorkDone(self._store.newTransaction, reactor, 60.0, ( GroupRefreshWork, GroupAttendeeReconciliationWork, GroupDelegateChangesWork, GroupShareeReconciliationWork, )) returnValue(self._ok("ok", "Group refresh scheduled"))
def test_cascade_delete_cleanup(self): """ Test that when work associated with L{txdav.caldav.datastore.scheduling.work.ScheduleWork} is removed with the L{ScheduleWork} item being removed, the associated L{JobItem} runs and removes itself and the L{ScheduleWork}. """ ScheduleWorkMixin._queued = 0 txn = self.transactionUnderTest() home = yield self.homeUnderTest(name="user01") yield ScheduleOrganizerWork.schedule( txn, "12345-67890", "create", home, None, None, self.calendar_new, "urn:uuid:user01", 2, True, ) yield self.commit() self.assertEqual(ScheduleWorkMixin._queued, 1) jobs = yield JobItem.all(self.transactionUnderTest()) work = yield jobs[0].workItem() yield WorkItem.delete(work) yield self.commit() jobs = yield JobItem.all(self.transactionUnderTest()) self.assertEqual(len(jobs), 1) baseWork = yield ScheduleWork.all(self.transactionUnderTest()) self.assertEqual(len(baseWork), 1) self.assertEqual(baseWork[0].jobID, jobs[0].jobID) work = yield jobs[0].workItem() self.assertTrue(work is None) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) jobs = yield JobItem.all(self.transactionUnderTest()) self.assertEqual(len(jobs), 0) work = yield ScheduleOrganizerWork.all(self.transactionUnderTest()) self.assertEqual(len(work), 0) baseWork = yield ScheduleWork.all(self.transactionUnderTest()) self.assertEqual(len(baseWork), 0)
def test_create(self): """ Test that jobs associated with L{txdav.caldav.datastore.scheduling.work.ScheduleOrganizerSendWork} can be created and correctly removed. """ txn = self.transactionUnderTest() home = yield self.homeUnderTest(name="user01") yield ScheduleOrganizerSendWork.schedule( txn, "create", home, None, "urn:x-uid:user01", "urn:x-uid:user02", self.itip_new, True, 1000, ) jobs = yield JobItem.all(self.transactionUnderTest()) self.assertEqual(len(jobs), 1) work = yield jobs[0].workItem() yield work.doWork() home2 = yield self.calendarUnderTest(home="user02", name="calendar") cobjs = yield home2.calendarObjects() self.assertEqual(len(cobjs), 1) #cal2 = yield cobjs[0].component() yield work.delete() yield jobs[0].delete() yield self.commit()
def stopIt(): txn = store.newTransaction() jobs = yield JobItem.all(txn) yield txn.commit() if enableJobProcessing: yield pool.stopService() # active transactions should have been shut down. wasBusy = len(cp._busy) busyText = repr(cp._busy) result = yield cp.stopService() if deriveValue(testCase, _SPECIAL_TXN_CLEAN, lambda tc: False): if wasBusy: testCase.fail("Outstanding Transactions: " + busyText) returnValue(result) if len(jobs): testCase.fail("Jobs left in job queue {}: {}".format( testCase, ",".join([job.workType for job in jobs]) )) returnValue(result)
def test_processReply(self): # Make sure an unknown token in an older email is deleted msg = email.message_from_string(self.dataFile('good_reply_past')) result = (yield self.receiver.processReply(msg)) self.assertEquals(result, MailReceiver.UNKNOWN_TOKEN_OLD) # Make sure an unknown token is not processed msg = email.message_from_string(self.dataFile('good_reply_future')) result = (yield self.receiver.processReply(msg)) self.assertEquals(result, MailReceiver.UNKNOWN_TOKEN) # Make sure a known token *is* processed txn = self.store.newTransaction() yield txn.imipCreateToken( "urn:x-uid:5A985493-EE2C-4665-94CF-4DFEA3A89500", "mailto:[email protected]", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f" ) yield txn.commit() result = (yield self.receiver.processReply(msg)) self.assertEquals(result, MailReceiver.INJECTION_SUBMITTED) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def afterWork(self): """ A hook that gets called after the L{WorkItem} does its real work. This can be used for common clean-up behaviors. The base implementation does nothing. """ yield super(ScheduleWorkMixin, self).afterWork() # Find the next item and schedule to run immediately after this. # We only coalesce ScheduleOrganizerSendWork. if self.workType() == ScheduleOrganizerSendWork.workType(): all = yield self.baseWork.query( self.transaction, (ScheduleWork.icalendarUid == self.icalendarUid).And( ScheduleWork.workID != self.workID), order=ScheduleWork.workID, limit=1, ) if all: work = all[0] if work.workType == self.workType(): job = yield JobItem.load(self.transaction, work.jobID) yield job.update(notBefore=datetime.datetime.utcnow()) log.debug( "ScheduleOrganizerSendWork - promoted job: {id}, UID: '{uid}'", id=work.workID, uid=self.icalendarUid)
def test_work(self): calendar = """BEGIN:VCALENDAR PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN VERSION:2.0 METHOD:REPLY BEGIN:VEVENT UID:12345-67890 DTSTAMP:20130208T120000Z DTSTART:20180601T120000Z DTEND:20180601T130000Z ORGANIZER:urn:x-uid:user01 ATTENDEE:mailto:[email protected];PARTSTAT=ACCEPTED END:VEVENT END:VCALENDAR """ txn = self.store.newTransaction() yield txn.enqueue( IMIPReplyWork, organizer="urn:x-uid:user01", attendee="mailto:[email protected]", icalendarText=calendar ) yield txn.commit() yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def test_inboxCleanupWorkQueueing(self): """ Verify that InboxCleanupWork queues one CleanupOneInboxBoxWork per home """ self.patch(config.InboxCleanup, "CleanupPeriodDays", -1) class FakeCleanupOneInboxWork(WorkItem): scheduledHomeIDs = [] @classmethod def reschedule(cls, txn, seconds, homeID): cls.scheduledHomeIDs.append(homeID) pass self.patch(CleanupOneInboxWork, "reschedule", FakeCleanupOneInboxWork.reschedule) # do cleanup yield InboxCleanupWork.reschedule(self.transactionUnderTest(), 0) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) ch = schema.CALENDAR_HOME workRows = yield Select( [ch.OWNER_UID], From=ch, Where=ch.RESOURCE_ID.In( Parameter("scheduledHomeIDs", len(FakeCleanupOneInboxWork.scheduledHomeIDs))), ).on(self.transactionUnderTest(), scheduledHomeIDs=FakeCleanupOneInboxWork.scheduledHomeIDs) homeUIDs = [workRow[0] for workRow in workRows] self.assertEqual(set(homeUIDs), set(['user01', 'user02'])) # two homes
def test_orphans(self): """ Verify that orphaned Inbox items are removed """ self.patch(config.InboxCleanup, "ItemLifetimeDays", -1) self.patch(config.InboxCleanup, "ItemLifeBeyondEventEndDays", -1) # create orphans by deleting events cal = yield self.calendarUnderTest(home="user01", name="calendar") for item in (yield cal.objectResourcesWithNames(["cal1.ics", "cal3.ics"])): yield item.remove() # do cleanup yield self.transactionUnderTest().enqueue( CleanupOneInboxWork, homeID=cal.ownerHome()._resourceID, notBefore=datetime.datetime.utcnow()) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) # check that orphans are deleted inbox = yield self.calendarUnderTest(home="user01", name="inbox") items = yield inbox.objectResources() names = [item.name() for item in items] self.assertEqual(set(names), set(["cal2.ics"]))
def test_referenceOldEvent(self): """ Verify that inbox items references old events are removed """ # events are already too old, so make one event end now calendar = yield self.calendarUnderTest(home="user01", name="calendar") cal3Event = yield calendar.objectResourceWithName("cal3.ics") tr = schema.TIME_RANGE yield Update( { tr.END_DATE: datetime.datetime.utcnow() }, Where=tr.CALENDAR_OBJECT_RESOURCE_ID == cal3Event._resourceID).on( self.transactionUnderTest()) # do cleanup yield self.transactionUnderTest().enqueue( CleanupOneInboxWork, homeID=calendar.ownerHome()._resourceID, notBefore=datetime.datetime.utcnow()) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) # check that old items are deleted inbox = yield self.calendarUnderTest(home="user01", name="inbox") items = yield inbox.objectResources() names = [item.name() for item in items] self.assertEqual(set(names), set(["cal3.ics"]))
def test_old(self): """ Verify that old inbox items are removed """ self.patch(config.InboxCleanup, "ItemLifeBeyondEventEndDays", -1) # Predate some inbox items inbox = yield self.calendarUnderTest(home="user01", name="inbox") oldDate = datetime.datetime.utcnow() - datetime.timedelta(days=float(config.InboxCleanup.ItemLifetimeDays), seconds=10) itemsToPredate = ["cal2.ics", "cal3.ics"] co = schema.CALENDAR_OBJECT yield Update({co.CREATED: oldDate}, Where=co.RESOURCE_NAME.In(Parameter("itemsToPredate", len(itemsToPredate))).And( co.CALENDAR_RESOURCE_ID == inbox._resourceID)).on(self.transactionUnderTest(), itemsToPredate=itemsToPredate) # do cleanup yield self.transactionUnderTest().enqueue(CleanupOneInboxWork, homeID=inbox.ownerHome()._resourceID, notBefore=datetime.datetime.utcnow()) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) # check that old items are deleted inbox = yield self.calendarUnderTest(home="user01", name="inbox") items = yield inbox.objectResources() names = [item.name() for item in items] self.assertEqual(set(names), set(["cal1.ics"]))
def test_create(self): """ Test that jobs associated with L{txdav.caldav.datastore.scheduling.work.ScheduleOrganizerSendWork} can be created and correctly removed. """ txn = self.transactionUnderTest() home = yield self.homeUnderTest(name="user01") yield ScheduleOrganizerSendWork.schedule( txn, "create", home, None, "urn:x-uid:user01", "urn:x-uid:user02", self.itip_new, True, 1000, ) jobs = yield JobItem.all(self.transactionUnderTest()) self.assertEqual(len(jobs), 1) work = yield jobs[0].workItem() yield work.doWork() home2 = yield self.calendarUnderTest(home="user02", name="calendar") cobjs = yield home2.calendarObjects() self.assertEqual(len(cobjs), 1) # cal2 = yield cobjs[0].component() yield work.delete() yield jobs[0].delete() yield self.commit()
def test_inboxCleanupWorkQueueing(self): """ Verify that InboxCleanupWork queues one CleanupOneInboxBoxWork per home """ self.patch(config.InboxCleanup, "CleanupPeriodDays", -1) class FakeCleanupOneInboxWork(WorkItem): scheduledHomeIDs = [] @classmethod def reschedule(cls, txn, seconds, homeID): cls.scheduledHomeIDs.append(homeID) pass self.patch(CleanupOneInboxWork, "reschedule", FakeCleanupOneInboxWork.reschedule) # do cleanup yield InboxCleanupWork.reschedule(self.transactionUnderTest(), 0) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) ch = schema.CALENDAR_HOME workRows = yield Select( [ch.OWNER_UID], From=ch, Where=ch.RESOURCE_ID.In(Parameter("scheduledHomeIDs", len(FakeCleanupOneInboxWork.scheduledHomeIDs))), ).on(self.transactionUnderTest(), scheduledHomeIDs=FakeCleanupOneInboxWork.scheduledHomeIDs) homeUIDs = [workRow[0] for workRow in workRows] self.assertEqual(set(homeUIDs), set(['user01', 'user02'])) # two homes
def test_processDSN(self): template = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN METHOD:REQUEST PRODID:-//example Inc.//iCal 3.0//EN BEGIN:VTIMEZONE TZID:US/Pacific BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU TZNAME:PDT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU TZNAME:PST TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C DTSTART;TZID=US/Pacific:20080812T094500 DTEND;TZID=US/Pacific:20080812T104500 ATTENDEE;CUTYPE=INDIVIDUAL;CN=User 01;PARTSTAT=ACCEPTED:mailto:user01@exam ple.com ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-A CTION;[email protected]:mailto:[email protected] CREATED:20080812T191857Z DTSTAMP:20080812T191932Z ORGANIZER;CN=User 01:mailto:xyzzy+%[email protected] SEQUENCE:2 SUMMARY:New Event TRANSP:OPAQUE END:VEVENT END:VCALENDAR """ # Make sure an unknown token is not processed calBody = template % "bogus_token" self.assertEquals((yield self.receiver.processDSN(calBody, "xyzzy")), MailReceiver.UNKNOWN_TOKEN) # Make sure a known token *is* processed txn = self.store.newTransaction() token = (yield txn.imipCreateToken( "urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500", "mailto:[email protected]", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C")) yield txn.commit() calBody = template % token result = (yield self.receiver.processDSN(calBody, "xyzzy")) self.assertEquals(result, MailReceiver.INJECTION_SUBMITTED) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def data_jobcount(self): """ Return a count of job types. @return: the JSON result. @rtype: L{int} """ return succeed(JobItem.numberOfWorkTypes())
def action_schedulingdone(self, j): """ Wait for all schedule queue items to complete. """ yield JobItem.waitWorkDone(self._store.newTransaction, reactor, 120.0, ( ScheduleOrganizerWork, ScheduleOrganizerSendWork, ScheduleReplyWork, ScheduleRefreshWork, ScheduleAutoReplyWork, )) returnValue(self._ok("ok", "Scheduling done"))
def _runOneJob(self): """ Run the first outstanding jobs. """ # Run jobs jobs = yield JobItem.all(self.transactionUnderTest()) for job in jobs: yield job.run() break yield self.commit()
def test_create(self): """ Test that jobs associated with L{txdav.caldav.datastore.scheduling.work.ScheduleOrganizerWork} can be created and correctly removed. """ ScheduleWorkMixin._queued = 0 txn = self.transactionUnderTest() home = yield self.homeUnderTest(name="user01") yield ScheduleOrganizerWork.schedule( txn, "12345-67890", "create", home, None, None, self.calendar_new, "urn:uuid:user01", 2, True, ) yield self.commit() self.assertEqual(ScheduleWorkMixin._queued, 1) jobs = yield JobItem.all(self.transactionUnderTest()) self.assertEqual(len(jobs), 1) work = yield jobs[0].workItem() self.assertTrue(isinstance(work, ScheduleOrganizerWork)) self.assertEqual(work.icalendarUid, "12345-67890") self.assertEqual(scheduleActionFromSQL[work.scheduleAction], "create") yield work.delete() yield jobs[0].delete() yield self.commit() jobs = yield JobItem.all(self.transactionUnderTest()) self.assertEqual(len(jobs), 0) work = yield ScheduleOrganizerWork.all(self.transactionUnderTest()) self.assertEqual(len(work), 0) baseWork = yield ScheduleWork.all(self.transactionUnderTest()) self.assertEqual(len(baseWork), 0)
def test_workFailure(self): self.sender.smtpSender.shouldSucceed = False txn = self.store.newTransaction() wp = (yield txn.enqueue(IMIPInvitationWork, fromAddr=ORGANIZER, toAddr=ATTENDEE, icalendarText=initialInviteText.replace("\n", "\r\n"), )) yield txn.commit() yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) # Verify a new work proposal was not created self.assertEquals(wp, self.wp)
def test_workFailure(self): self.sender.smtpSender.shouldSucceed = False txn = self.store.newTransaction() wp = (yield txn.enqueue( IMIPInvitationWork, fromAddr=ORGANIZER, toAddr=ATTENDEE, icalendarText=initialInviteText.replace("\n", "\r\n"), )) yield txn.commit() yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) # Verify a new work proposal was not created self.assertEquals(wp, self.wp)
def test_update_delete_old_nonextant(self): """ Verify that old missing groups are deleted from group cache """ oldGroupPurgeIntervalSeconds = config.AutomaticPurging.GroupPurgeIntervalSeconds store = self.storeUnderTest() for uid in ( u"testgroup", u"emptygroup", ): config.AutomaticPurging.GroupPurgeIntervalSeconds = oldGroupPurgeIntervalSeconds txn = store.newTransaction() groupID = (yield txn.groupByUID(uid))[0] yield txn.addDelegateGroup(delegator=u"sagen", delegateGroupID=groupID, readWrite=True) (groupID, _ignore_name, _ignore_membershipHash, _ignore_modified, extant) = yield txn.groupByUID(uid, create=False) yield txn.commit() self.assertTrue(extant) self.assertNotEqual(groupID, None) # Remove the group, still cached yield self.directory.removeRecords([uid]) txn = store.newTransaction() yield self.groupCacher.update(txn) (groupID, _ignore_name, _ignore_membershipHash, _ignore_modified, extant) = yield txn.groupByUID(uid, create=False) yield txn.commit() yield JobItem.waitEmpty(store.newTransaction, reactor, 60) txn = store.newTransaction() (groupID, _ignore_name, _ignore_membershipHash, _ignore_modified, extant) = yield txn.groupByUID(uid, create=False) yield txn.commit() self.assertNotEqual(groupID, None) self.assertFalse(extant) # delete the group config.AutomaticPurging.GroupPurgeIntervalSeconds = "0.0" txn = store.newTransaction() yield self.groupCacher.update(txn) groupID = (yield txn.groupByUID(uid, create=False))[0] yield txn.commit() self.assertEqual(groupID, None)
def test_processReplyMissingAttendee(self): msg = email.message_from_string( self.dataFile('reply_missing_attendee')) txn = self.store.newTransaction() yield txn.imipCreateToken( "urn:x-uid:5A985493-EE2C-4665-94CF-4DFEA3A89500", "mailto:[email protected]", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f") yield txn.commit() result = (yield self.receiver.processReply(msg)) self.assertEquals(result, MailReceiver.INJECTION_SUBMITTED) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def checkTemporaryFailure(self, results): """ Check to see whether whether a temporary failure should be raised as opposed to continuing on with a permanent failure. @param results: set of results gathered in L{extractSchedulingResponse} @type results: L{list} """ if all([result[1] == iTIPRequestStatus.MESSAGE_PENDING_CODE for result in results]): job = yield JobItem.load(self.transaction, self.jobID) if job.failed >= config.Scheduling.Options.WorkQueues.MaxTemporaryFailures: # Set results to SERVICE_UNAVAILABLE for ctr, result in enumerate(results): results[ctr] = (result[0], iTIPRequestStatus.SERVICE_UNAVAILABLE_CODE,) returnValue(None) else: raise JobTemporaryError(config.Scheduling.Options.WorkQueues.TemporaryFailureDelay)
def data_jobs(self): """ Return a summary of the job queue. @return: a string containing the JSON result. @rtype: L{str} """ if self.factory.store: txn = self.factory.store.newTransaction() records = (yield JobItem.histogram(txn)) yield txn.commit() else: records = {} returnValue(records)
def test_processReplyMissingAttendee(self): msg = email.message_from_string(self.dataFile('reply_missing_attendee')) txn = self.store.newTransaction() yield txn.imipCreateToken( "urn:x-uid:5A985493-EE2C-4665-94CF-4DFEA3A89500", "mailto:[email protected]", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f" ) yield txn.commit() result = (yield self.receiver.processReply(msg)) self.assertEquals(result, MailReceiver.INJECTION_SUBMITTED) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def test_processReplyMissingAttachment(self): msg = email.message_from_string( self.dataFile('reply_missing_attachment')) # stick the token in the database first txn = self.store.newTransaction() yield txn.imipCreateToken( "urn:x-uid:5A985493-EE2C-4665-94CF-4DFEA3A89500", "mailto:[email protected]", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f") yield txn.commit() result = (yield self.receiver.processReply(msg)) self.assertEquals(result, MailReceiver.REPLY_FORWARDED_TO_ORGANIZER) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def test_ImportComponentOrganizer(self): component = Component.allFromString(DATA_WITH_ORGANIZER) yield importCollectionComponent(self.store, component) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) 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, "I'm the organizer"), (customxml.CalendarColor, "#0000FFFF"), ): self.assertEquals( value, collectionProperties[PropertyName.fromElement(element)] ) # Verify the organizer's child objects objects = yield collection.listObjectResources() self.assertEquals(len(objects), 1) # Verify the attendees' child objects home = yield txn.calendarHomeWithUID("user02") collection = yield home.childWithName("calendar") objects = yield collection.listObjectResources() self.assertEquals(len(objects), 1) home = yield txn.calendarHomeWithUID("user03") collection = yield home.childWithName("calendar") objects = yield collection.listObjectResources() self.assertEquals(len(objects), 1) home = yield txn.calendarHomeWithUID("mercury") collection = yield home.childWithName("calendar") objects = yield collection.listObjectResources() self.assertEquals(len(objects), 1) yield txn.commit()
def test_iMIP_delivery(self): data = """BEGIN:VCALENDAR VERSION:2.0 METHOD:REQUEST PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z ORGANIZER;CN="User 01":mailto:[email protected] ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] END:VEVENT END:VCALENDAR """ results = [] class FakeSender(object): def outbound(self, txn, fromAddr, toAddr, calendar): results.append((fromAddr, toAddr)) return succeed(None) self.patch(IMIPInvitationWork, "mailSender", FakeSender()) scheduler = iMIPProcessing.FakeSchedule( LocalCalendarUser("mailto:[email protected]", None), Component.fromString(data) ) scheduler.txn = self.transactionUnderTest() recipients = (RemoteCalendarUser("mailto:[email protected]"),) responses = ScheduleResponseQueue("REQUEST", responsecode.OK) delivery = ScheduleViaIMip(scheduler, recipients, responses, False) yield delivery.generateSchedulingResponses() self.assertEqual(len(responses.responses), 1) self.assertEqual(str(responses.responses[0].reqstatus), iTIPRequestStatus.MESSAGE_SENT) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) self.assertEqual(len(results), 1) self.assertEqual(results[0], ("mailto:[email protected]", "mailto:[email protected]",))
def test_processReply(self): msg = email.message_from_string(self.dataFile('good_reply')) # Make sure an unknown token is not processed result = (yield self.receiver.processReply(msg)) self.assertEquals(result, MailReceiver.UNKNOWN_TOKEN) # Make sure a known token *is* processed txn = self.store.newTransaction() yield txn.imipCreateToken( "urn:uuid:5A985493-EE2C-4665-94CF-4DFEA3A89500", "mailto:[email protected]", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f") yield txn.commit() result = (yield self.receiver.processReply(msg)) self.assertEquals(result, MailReceiver.INJECTION_SUBMITTED) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def test_work(self): txn = self.store.newTransaction() wp = (yield txn.enqueue( IMIPInvitationWork, fromAddr=ORGANIZER, toAddr=ATTENDEE, icalendarText=initialInviteText.replace("\n", "\r\n"), )) self.assertEquals(wp, self.wp) yield txn.commit() yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) txn = self.store.newTransaction() token = (yield txn.imipGetToken(ORGANIZER, ATTENDEE, ICALUID)) self.assertTrue(token) organizer, attendee, icaluid = (yield txn.imipLookupByToken(token))[0] yield txn.commit() self.assertEquals(organizer, ORGANIZER) self.assertEquals(attendee, ATTENDEE) self.assertEquals(icaluid, ICALUID)
def test_processReplyMissingAttachment(self): msg = email.message_from_string( self.dataFile('reply_missing_attachment') ) # stick the token in the database first txn = self.store.newTransaction() yield txn.imipCreateToken( "urn:x-uid:5A985493-EE2C-4665-94CF-4DFEA3A89500", "mailto:[email protected]", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f" ) yield txn.commit() result = (yield self.receiver.processReply(msg)) self.assertEquals(result, MailReceiver.REPLY_FORWARDED_TO_ORGANIZER) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def test_ImportComponentOrganizer(self): component = Component.allFromString(DATA_WITH_ORGANIZER) yield importCollectionComponent(self.store, component) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) 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, "I'm the organizer"), (customxml.CalendarColor, "#0000FFFF"), ): self.assertEquals( value, collectionProperties[PropertyName.fromElement(element)]) # Verify the organizer's child objects objects = yield collection.listObjectResources() self.assertEquals(len(objects), 1) # Verify the attendees' child objects home = yield txn.calendarHomeWithUID("user02") collection = yield home.childWithName("calendar") objects = yield collection.listObjectResources() self.assertEquals(len(objects), 1) home = yield txn.calendarHomeWithUID("user03") collection = yield home.childWithName("calendar") objects = yield collection.listObjectResources() self.assertEquals(len(objects), 1) home = yield txn.calendarHomeWithUID("mercury") collection = yield home.childWithName("calendar") objects = yield collection.listObjectResources() self.assertEquals(len(objects), 1) yield txn.commit()
def test_orphans(self): """ Verify that orphaned Inbox items are removed """ self.patch(config.InboxCleanup, "ItemLifetimeDays", -1) self.patch(config.InboxCleanup, "ItemLifeBeyondEventEndDays", -1) #create orphans by deleting events inbox = yield self.calendarUnderTest(home="user01", name="inbox") for item in (yield inbox.objectResourcesWithNames(["cal1.ics", "cal3.ics"])): yield item.remove() # do cleanup yield self.transactionUnderTest().enqueue(CleanupOneInboxWork, homeID=inbox.ownerHome()._resourceID, notBefore=datetime.datetime.utcnow()) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) # check that orphans are deleted inbox = yield self.calendarUnderTest(home="user01", name="inbox") items = yield inbox.objectResources() names = [item.name() for item in items] self.assertEqual(set(names), set(["cal2.ics"]))
def test_referenceOldEvent(self): """ Verify that inbox items references old events are removed """ # events are already too old, so make one event end now calendar = yield self.calendarUnderTest(home="user01", name="calendar") cal3Event = yield calendar.objectResourceWithName("cal3.ics") tr = schema.TIME_RANGE yield Update({tr.END_DATE: datetime.datetime.utcnow()}, Where=tr.CALENDAR_OBJECT_RESOURCE_ID == cal3Event._resourceID).on( self.transactionUnderTest()) # do cleanup yield self.transactionUnderTest().enqueue(CleanupOneInboxWork, homeID=calendar.ownerHome()._resourceID, notBefore=datetime.datetime.utcnow()) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) # check that old items are deleted inbox = yield self.calendarUnderTest(home="user01", name="inbox") items = yield inbox.objectResources() names = [item.name() for item in items] self.assertEqual(set(names), set(["cal3.ics"]))
def afterWork(self): """ A hook that gets called after the L{WorkItem} does its real work. This can be used for common clean-up behaviors. The base implementation does nothing. """ yield super(ScheduleWorkMixin, self).afterWork() # Find the next item and schedule to run immediately after this. # We only coalesce ScheduleOrganizerSendWork. if self.workType() == ScheduleOrganizerSendWork.workType(): all = yield self.baseWork.query( self.transaction, (ScheduleWork.icalendarUid == self.icalendarUid).And(ScheduleWork.workID != self.workID), order=ScheduleWork.workID, limit=1, ) if all: work = all[0] if work.workType == self.workType(): job = yield JobItem.load(self.transaction, work.jobID) yield job.update(notBefore=datetime.datetime.utcnow()) log.debug("ScheduleOrganizerSendWork - promoted job: {id}, UID: '{uid}'", id=work.workID, uid=self.icalendarUid)
def stopIt(): txn = store.newTransaction() jobs = yield JobItem.all(txn) yield txn.commit() if len(jobs): print("Jobs left in job queue {}: {}".format( testCase, ",".join([job.workType for job in jobs]) )) if enableJobProcessing: yield pool.stopService() # active transactions should have been shut down. wasBusy = len(cp._busy) busyText = repr(cp._busy) result = yield cp.stopService() if deriveValue(testCase, _SPECIAL_TXN_CLEAN, lambda tc: False): if wasBusy: testCase.fail("Outstanding Transactions: " + busyText) returnValue(result) returnValue(result)
def test_work(self): txn = self.store.newTransaction() wp = (yield txn.enqueue(IMIPInvitationWork, fromAddr=ORGANIZER, toAddr=ATTENDEE, icalendarText=initialInviteText.replace("\n", "\r\n"), )) self.assertEquals(wp, self.wp) yield txn.commit() yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60) txn = self.store.newTransaction() token = (yield txn.imipGetToken( ORGANIZER, ATTENDEE, ICALUID )) self.assertTrue(token) organizer, attendee, icaluid = (yield txn.imipLookupByToken(token))[0] yield txn.commit() self.assertEquals(organizer, ORGANIZER) self.assertEquals(attendee, ATTENDEE) self.assertEquals(icaluid, ICALUID)
def test_work(self): calendar = """BEGIN:VCALENDAR PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN VERSION:2.0 METHOD:REPLY BEGIN:VEVENT UID:12345-67890 DTSTAMP:20130208T120000Z DTSTART:20180601T120000Z DTEND:20180601T130000Z ORGANIZER:urn:x-uid:user01 ATTENDEE:mailto:[email protected];PARTSTAT=ACCEPTED END:VEVENT END:VCALENDAR """ txn = self.store.newTransaction() yield txn.enqueue(IMIPReplyWork, organizer="urn:x-uid:user01", attendee="mailto:[email protected]", icalendarText=calendar) yield txn.commit() yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def test_old(self): """ Verify that old inbox items are removed """ self.patch(config.InboxCleanup, "ItemLifeBeyondEventEndDays", -1) # Predate some inbox items inbox = yield self.calendarUnderTest(home="user01", name="inbox") oldDate = datetime.datetime.utcnow() - datetime.timedelta( days=float(config.InboxCleanup.ItemLifetimeDays), seconds=10) itemsToPredate = ["cal2.ics", "cal3.ics"] co = schema.CALENDAR_OBJECT yield Update({ co.CREATED: oldDate }, Where=co.RESOURCE_NAME.In( Parameter("itemsToPredate", len(itemsToPredate))).And( co.CALENDAR_RESOURCE_ID == inbox._resourceID)).on( self.transactionUnderTest(), itemsToPredate=itemsToPredate) # do cleanup yield self.transactionUnderTest().enqueue( CleanupOneInboxWork, homeID=inbox.ownerHome()._resourceID, notBefore=datetime.datetime.utcnow()) yield self.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) # check that old items are deleted inbox = yield self.calendarUnderTest(home="user01", name="inbox") items = yield inbox.objectResources() names = [item.name() for item in items] self.assertEqual(set(names), set(["cal1.ics"]))
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)
def postCheck(self): """ Checks after migration is done """ # Check that the home has been moved home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="user01") self.assertTrue(home.external()) home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="user01", status=_HOME_STATUS_NORMAL) self.assertTrue(home is None) home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="user01", status=_HOME_STATUS_EXTERNAL) self.assertTrue(home is not None) home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="user01", status=_HOME_STATUS_DISABLED) self.assertTrue(home is not None) home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="user01", status=_HOME_STATUS_MIGRATING) self.assertTrue(home is None) yield self.commitTransaction(0) home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="user01") self.assertTrue(home.normal()) home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="user01", status=_HOME_STATUS_NORMAL) self.assertTrue(home is not None) home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="user01", status=_HOME_STATUS_EXTERNAL) self.assertTrue(home is None) home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="user01", status=_HOME_STATUS_DISABLED) self.assertTrue(home is not None) home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="user01", status=_HOME_STATUS_MIGRATING) self.assertTrue(home is None) yield self.commitTransaction(1) # Check that the notifications have been moved notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(0), name="user01", status=_HOME_STATUS_NORMAL) self.assertTrue(notifications is None) notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(0), name="user01", status=_HOME_STATUS_EXTERNAL) self.assertTrue(notifications is None) notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(0), name="user01", status=_HOME_STATUS_DISABLED) self.assertTrue(notifications is not None) yield self.commitTransaction(0) notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(1), name="user01", status=_HOME_STATUS_NORMAL) self.assertTrue(notifications is not None) notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(1), name="user01", status=_HOME_STATUS_EXTERNAL) self.assertTrue(notifications is None) notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(1), name="user01", status=_HOME_STATUS_DISABLED) self.assertTrue(notifications is not None) yield self.commitTransaction(1) # New pod data homes = {} homes["user01"] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="user01") homes["user02"] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="user02") self.assertTrue(homes["user02"].external()) homes["user03"] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="user03") self.assertTrue(homes["user03"].external()) homes["puser01"] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="puser01") self.assertTrue(homes["puser01"].normal()) homes["puser02"] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="puser02") self.assertTrue(homes["puser02"].normal()) homes["puser03"] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name="puser03") self.assertTrue(homes["puser03"].normal()) # Check calendar data on new pod calendars = yield homes["user01"].loadChildren() calnames = dict([(calendar.name(), calendar) for calendar in calendars]) self.assertEqual( set(calnames.keys()), set(("calendar", "tasks", "inbox", self.stash["sharename_user02_to_user01"], self.stash["sharename_puser02_to_user01"],)) ) # Check shared-by user01 on new pod shared = calnames["calendar"] invitations = yield shared.sharingInvites() by_sharee = dict([(invitation.shareeUID, invitation) for invitation in invitations]) self.assertEqual(len(invitations), 2) self.assertEqual(set(by_sharee.keys()), set(("user03", "puser03",))) self.assertEqual(by_sharee["user03"].shareeHomeID, homes["user03"].id()) self.assertEqual(by_sharee["puser03"].shareeHomeID, homes["puser03"].id()) # Check shared-to user01 on new pod shared = calnames[self.stash["sharename_user02_to_user01"]] self.assertEqual(shared.ownerHome().uid(), "user02") self.assertEqual(shared.ownerHome().id(), homes["user02"].id()) shared = calnames[self.stash["sharename_puser02_to_user01"]] self.assertEqual(shared.ownerHome().uid(), "puser02") self.assertEqual(shared.ownerHome().id(), homes["puser02"].id()) shared = yield homes["puser02"].calendarWithName("calendar") invitations = yield shared.sharingInvites() self.assertEqual(len(invitations), 1) self.assertEqual(invitations[0].shareeHomeID, homes["user01"].id()) yield self.commitTransaction(1) # Old pod data homes = {} homes["user01"] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="user01") homes["user02"] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="user02") self.assertTrue(homes["user02"].normal()) homes["user03"] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="user03") self.assertTrue(homes["user03"].normal()) homes["puser01"] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="puser01") self.assertTrue(homes["puser01"] is None) homes["puser02"] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="puser02") self.assertTrue(homes["puser02"].external()) homes["puser03"] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name="puser03") self.assertTrue(homes["puser03"].external()) # Check shared-by user01 on old pod shared = yield homes["user03"].calendarWithName(self.stash["sharename_user01_to_user03"]) self.assertEqual(shared.ownerHome().uid(), "user01") self.assertEqual(shared.ownerHome().id(), homes["user01"].id()) # Check shared-to user01 on old pod shared = yield homes["user02"].calendarWithName("calendar") invitations = yield shared.sharingInvites() self.assertEqual(len(invitations), 1) self.assertEqual(invitations[0].shareeHomeID, homes["user01"].id()) yield self.commitTransaction(0) # Delegates on each pod for pod in range(self.numberOfStores): txn = self.theTransactionUnderTest(pod) records = {} for ctr in range(10): uid = u"user{:02d}".format(ctr + 1) records[uid] = yield txn.directoryService().recordWithUID(uid) for ctr in range(10): uid = u"puser{:02d}".format(ctr + 1) records[uid] = yield txn.directoryService().recordWithUID(uid) for ctr in range(10): uid = u"group{:02d}".format(ctr + 1) records[uid] = yield txn.directoryService().recordWithUID(uid) delegates = yield Delegates.delegatesOf(txn, records["user01"], True, False) self.assertTrue(records["user02"] in delegates) self.assertTrue(records["group02"] in delegates) delegates = yield Delegates.delegatesOf(txn, records["user01"], True, True) self.assertTrue(records["user02"] in delegates) self.assertTrue(records["user06"] in delegates) self.assertTrue(records["user07"] in delegates) self.assertTrue(records["user08"] in delegates) delegates = yield Delegates.delegatesOf(txn, records["user01"], False, False) self.assertTrue(records["user03"] in delegates) self.assertTrue(records["group03"] in delegates) self.assertTrue(records["puser01"] in delegates) delegates = yield Delegates.delegatesOf(txn, records["user01"], False, True) self.assertTrue(records["user03"] in delegates) self.assertTrue(records["user07"] in delegates) self.assertTrue(records["user08"] in delegates) self.assertTrue(records["user09"] in delegates) self.assertTrue(records["puser01"] in delegates) # Attachments obj = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), name="01_3.ics", calendar_name="calendar", home="user01") attachment = yield obj.attachmentWithManagedID(self.stash["user01_attachment_mid"]) self.assertTrue(attachment is not None) self.assertEqual(attachment.md5(), self.stash["user01_attachment_md5"]) data = yield self.attachmentToString(attachment) self.assertEqual(data, "Here is some text #1.") # Check removal of data from new pod # Make sure all jobs are done yield JobItem.waitEmpty(self.theStoreUnderTest(1).newTransaction, reactor, 60) # No migration state data left txn = self.theTransactionUnderTest(1) for migrationType in (CalendarMigrationRecord, CalendarObjectMigrationRecord, AttachmentMigrationRecord,): records = yield migrationType.all(txn) self.assertEqual(len(records), 0, msg=migrationType.__name__) yield self.commitTransaction(1) # No homes txn = self.theTransactionUnderTest(1) oldhome = yield txn.calendarHomeWithUID("user01", status=_HOME_STATUS_DISABLED) self.assertTrue(oldhome is None) oldhome = yield txn.notificationsWithUID("user01", status=_HOME_STATUS_DISABLED) self.assertTrue(oldhome is None) # Check removal of data from old pod # Make sure all jobs are done yield JobItem.waitEmpty(self.theStoreUnderTest(0).newTransaction, reactor, 60) # No homes txn = self.theTransactionUnderTest(0) oldhome = yield txn.calendarHomeWithUID("user01", status=_HOME_STATUS_DISABLED) self.assertTrue(oldhome is None) oldhome = yield txn.notificationsWithUID("user01", status=_HOME_STATUS_DISABLED) self.assertTrue(oldhome is None) # No delegates for delegateType in (DelegateRecord, DelegateGroupsRecord, ExternalDelegateGroupsRecord): records = yield delegateType.query(txn, delegateType.delegator == "user01") self.assertEqual(len(records), 0, msg=delegateType.__name__) # No work items for workType in allScheduleWork: records = yield workType.query(txn, workType.homeResourceID == self.stash["user01_pod0_home_id"]) self.assertEqual(len(records), 0, msg=workType.__name__)
def waitAllEmpty(self): for i in range(self.numberOfStores): yield JobItem.waitEmpty( self.theStoreUnderTest(i).newTransaction, reactor, 60.0 )
def test_work(self): pushDistributor = StubDistributor() def decorateTransaction(txn): txn._pushDistributor = pushDistributor self._sqlCalendarStore.callWithNewTransactions(decorateTransaction) txn = self._sqlCalendarStore.newTransaction() yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/foo/", pushPriority=PushPriority.high.value ) yield txn.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) self.assertEquals(pushDistributor.history, [("/CalDAV/localhost/foo/", PushPriority.high)]) pushDistributor.reset() txn = self._sqlCalendarStore.newTransaction() yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.high.value ) yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.high.value ) yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.high.value ) # Enqueue a different pushID to ensure those are not grouped with # the others: yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/baz/", pushPriority=PushPriority.high.value ) yield txn.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) self.assertEquals(set(pushDistributor.history), set([("/CalDAV/localhost/bar/", PushPriority.high), ("/CalDAV/localhost/baz/", PushPriority.high)])) # Ensure only the high-water-mark priority push goes out, by # enqueuing low, medium, and high notifications pushDistributor.reset() txn = self._sqlCalendarStore.newTransaction() yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.low.value ) yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.high.value ) yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.medium.value ) yield txn.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) self.assertEquals(pushDistributor.history, [("/CalDAV/localhost/bar/", PushPriority.high)])
def poll(self): if self._polling: return self._polling = True txn = self._store.newTransaction() try: # Look up all of the jobs events = [] jobsByTypeName = {} for job in (yield JobItem.all(txn)): jobsByTypeName.setdefault(job.workType, []).append(job) totalsByTypeName = {} for workType in JobItem.workTypes(): typeName = workType.table.model.name jobs = jobsByTypeName.get(typeName, []) totalsByTypeName[typeName] = len(jobs) jobDicts = [] for job in jobs: def formatTime(datetime): if datetime is None: return None else: # FIXME: Use HTTP time format return datetime.ctime() jobDict = dict( job_jobID=job.jobID, job_priority=job.priority, job_weight=job.weight, job_notBefore=formatTime(job.notBefore), ) work = yield job.workItem() attrs = ("workID", "group") if workType == PushNotificationWork: attrs += ("pushID", "priority") elif workType == ScheduleOrganizerWork: attrs += ("icalendarUid", "attendeeCount") elif workType == ScheduleRefreshWork: attrs += ("icalendarUid", "attendeeCount") elif workType == ScheduleReplyWork: attrs += ("icalendarUid", ) elif workType == ScheduleAutoReplyWork: attrs += ("icalendarUid", ) elif workType == GroupCacherPollingWork: attrs += () elif workType == IMIPPollingWork: attrs += () elif workType == IMIPReplyWork: attrs += ("organizer", "attendee") else: attrs = () if attrs: if work is None: self.log.error( "workItem() returned None for job: {job}", job=job) # jobDict.update((attr, None) for attr in attrs) for attr in attrs: jobDict["work_{}".format(attr)] = None else: # jobDict.update( # ("work_{}".format(attr), getattr(work, attr)) # for attr in attrs # ) for attr in attrs: jobDict["work_{}".format(attr)] = (getattr( work, attr)) jobDicts.append(jobDict) if jobDicts: events.append( dict( eventClass=typeName, eventID=time(), eventText=asJSON(jobDicts), )) events.append( dict( eventClass=u"work-total", eventID=time(), eventText=asJSON(totalsByTypeName), eventRetry=(self._pollInterval), )) # Send data self.addEvents(events) except: self._polling = False yield txn.abort() raise else: yield txn.commit() # Schedule the next poll if not hasattr(self, "_clock"): from twisted.internet import reactor self._clock = reactor self._clock.callLater(self._pollInterval / 1000, self.poll)
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_work(self): self.patch(JobItem, "failureRescheduleInterval", 2) pushDistributor = StubDistributor() def decorateTransaction(txn): txn._pushDistributor = pushDistributor self._sqlCalendarStore.callWithNewTransactions(decorateTransaction) txn = self._sqlCalendarStore.newTransaction() yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/foo/", pushPriority=PushPriority.high.value ) yield txn.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) self.assertEquals(pushDistributor.history, [("/CalDAV/localhost/foo/", PushPriority.high)]) pushDistributor.reset() txn = self._sqlCalendarStore.newTransaction() yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.high.value ) yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.high.value ) yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.high.value ) # Enqueue a different pushID to ensure those are not grouped with # the others: yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/baz/", pushPriority=PushPriority.high.value ) yield txn.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) self.assertEquals(set(pushDistributor.history), set([("/CalDAV/localhost/bar/", PushPriority.high), ("/CalDAV/localhost/baz/", PushPriority.high)])) # Ensure only the high-water-mark priority push goes out, by # enqueuing low, medium, and high notifications pushDistributor.reset() txn = self._sqlCalendarStore.newTransaction() yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.low.value ) yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.high.value ) yield txn.enqueue(PushNotificationWork, pushID="/CalDAV/localhost/bar/", pushPriority=PushPriority.medium.value ) yield txn.commit() yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60) self.assertEquals(pushDistributor.history, [("/CalDAV/localhost/bar/", PushPriority.high)])
def test_processDSN(self): template = """BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN METHOD:REQUEST PRODID:-//example Inc.//iCal 3.0//EN BEGIN:VTIMEZONE TZID:US/Pacific BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU TZNAME:PDT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU TZNAME:PST TZOFFSETFROM:-0700 TZOFFSETTO:-0800 END:STANDARD END:VTIMEZONE BEGIN:VEVENT UID:1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C DTSTART;TZID=US/Pacific:20080812T094500 DTEND;TZID=US/Pacific:20080812T104500 ATTENDEE;CUTYPE=INDIVIDUAL;CN=User 01;PARTSTAT=ACCEPTED:mailto:user01@exam ple.com ATTENDEE;CUTYPE=INDIVIDUAL;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-A CTION;[email protected]:mailto:[email protected] CREATED:20080812T191857Z DTSTAMP:20080812T191932Z ORGANIZER;CN=User 01:mailto:xyzzy+%[email protected] SEQUENCE:2 SUMMARY:New Event TRANSP:OPAQUE END:VEVENT END:VCALENDAR """ # Make sure an unknown token is not processed calBody = template % "bogus_token" self.assertEquals( (yield self.receiver.processDSN(calBody, "xyzzy")), MailReceiver.UNKNOWN_TOKEN ) # Make sure a known token *is* processed txn = self.store.newTransaction() record = (yield txn.imipCreateToken( "urn:x-uid:5A985493-EE2C-4665-94CF-4DFEA3A89500", "mailto:[email protected]", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C" )) yield txn.commit() calBody = template % record.token result = (yield self.receiver.processDSN(calBody, "xyzzy")) self.assertEquals(result, MailReceiver.INJECTION_SUBMITTED) yield JobItem.waitEmpty(self.store.newTransaction, reactor, 60)
def poll(self): if self._polling: return self._polling = True txn = self._store.newTransaction() try: # Look up all of the jobs events = [] jobsByTypeName = {} for job in (yield JobItem.all(txn)): jobsByTypeName.setdefault(job.workType, []).append(job) totalsByTypeName = {} for workType in JobItem.workTypes(): typeName = workType.table.model.name jobs = jobsByTypeName.get(typeName, []) totalsByTypeName[typeName] = len(jobs) jobDicts = [] for job in jobs: def formatTime(datetime): if datetime is None: return None else: # FIXME: Use HTTP time format return datetime.ctime() jobDict = dict( job_jobID=job.jobID, job_priority=job.priority, job_weight=job.weight, job_notBefore=formatTime(job.notBefore), ) work = yield job.workItem() attrs = ("workID", "group") if workType == PushNotificationWork: attrs += ("pushID", "priority") elif workType == ScheduleOrganizerWork: attrs += ("icalendarUid", "attendeeCount") elif workType == ScheduleRefreshWork: attrs += ("icalendarUid", "attendeeCount") elif workType == ScheduleReplyWork: attrs += ("icalendarUid",) elif workType == ScheduleAutoReplyWork: attrs += ("icalendarUid",) elif workType == GroupCacherPollingWork: attrs += () elif workType == IMIPPollingWork: attrs += () elif workType == IMIPReplyWork: attrs += ("organizer", "attendee") else: attrs = () if attrs: if work is None: self.log.error( "workItem() returned None for job: {job}", job=job ) # jobDict.update((attr, None) for attr in attrs) for attr in attrs: jobDict["work_{}".format(attr)] = None else: # jobDict.update( # ("work_{}".format(attr), getattr(work, attr)) # for attr in attrs # ) for attr in attrs: jobDict["work_{}".format(attr)] = ( getattr(work, attr) ) jobDicts.append(jobDict) if jobDicts: events.append(dict( eventClass=typeName, eventID=time(), eventText=asJSON(jobDicts), )) events.append(dict( eventClass=u"work-total", eventID=time(), eventText=asJSON(totalsByTypeName), eventRetry=(self._pollInterval), )) # Send data self.addEvents(events) except: self._polling = False yield txn.abort() raise else: yield txn.commit() # Schedule the next poll if not hasattr(self, "_clock"): from twisted.internet import reactor self._clock = reactor self._clock.callLater(self._pollInterval / 1000, self.poll)
def test_replyBeforeResourceDelete(self): """ Test that a reply is sent if an attendee changes an event, then immediately deletes it. """ organizer1 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION:urn:x-uid:user02 END:VEVENT END:VCALENDAR """) attendee1 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION;RSVP=TRUE:urn:x-uid:user02 TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """) organizer2 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=DECLINED;SCHEDULE-STATUS=2.0:urn:x-uid:user02 END:VEVENT END:VCALENDAR """) attendee2 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=DECLINED:urn:x-uid:user02 TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """) yield self.createOrganizerEvent("user01", organizer1) attendee = yield self.getAttendeeEvent("user02") self.assertEqual(attendee, attendee1, msg=diff_iCalStrs(attendee, attendee1)) yield self.setAttendeeEvent("user02", attendee2, run_jobs=False) calobj = yield self.getAttendeeResource("user02") yield calobj.remove() yield self.commit() yield self._runAllJobs() jobs = yield JobItem.all(self.transactionUnderTest()) self.assertEqual(len(jobs), 0) yield self.commit() organizer = yield self.getOrganizerEvent("user01") self.assertEqual(organizer, organizer2, msg=diff_iCalStrs(organizer, organizer2))
def test_update_delete_unused(self): """ Verify that unused groups are deleted from group cache """ store = self.storeUnderTest() # unused group deleted for uid in ( u"testgroup", u"emptygroup", ): txn = store.newTransaction() yield self.groupCacher.refreshGroup(txn, uid) groupID = (yield txn.groupByUID(uid, create=False))[0] yield txn.commit() self.assertNotEqual(groupID, None) txn = store.newTransaction() yield self.groupCacher.update(txn) groupID = (yield txn.groupByUID(uid, create=False))[0] yield txn.commit() self.assertEqual(groupID, None) # delegate groups not deleted for uid in ( u"testgroup", u"emptygroup", ): txn = store.newTransaction() groupID = (yield txn.groupByUID(uid))[0] yield txn.addDelegateGroup(delegator=u"sagen", delegateGroupID=groupID, readWrite=True) yield txn.commit() self.assertNotEqual(groupID, None) txn = store.newTransaction() yield self.groupCacher.update(txn) yield txn.commit() yield JobItem.waitEmpty(store.newTransaction, reactor, 60) txn = store.newTransaction() groupID = (yield txn.groupByUID(uid, create=False))[0] yield txn.commit() self.assertNotEqual(groupID, None) # delegate group is deleted. unused group is deleted txn = store.newTransaction() testGroupID = (yield txn.groupByUID(u"testgroup", create=False))[0] yield txn.removeDelegateGroup(delegator=u"sagen", delegateGroupID=testGroupID, readWrite=True) testGroupID = (yield txn.groupByUID(u"testgroup", create=False))[0] emptyGroupID = (yield txn.groupByUID(u"emptygroup", create=False))[0] yield txn.commit() self.assertNotEqual(testGroupID, None) self.assertNotEqual(emptyGroupID, None) txn = store.newTransaction() yield self.groupCacher.update(txn) yield txn.commit() yield JobItem.waitEmpty(store.newTransaction, reactor, 60) txn = store.newTransaction() testGroupID = (yield txn.groupByUID(u"testgroup", create=False))[0] emptyGroupID = (yield txn.groupByUID(u"emptygroup", create=False))[0] yield txn.commit() self.assertEqual(testGroupID, None) self.assertNotEqual(emptyGroupID, None)
def test_replyBeforeOrganizerEXDATE(self): """ Test that a reply is sent if an attendee changes an event, but the organizer exdate's the instance before the reply work is processed. """ organizer1 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION:urn:x-uid:user02 RRULE:FREQ=DAILY END:VEVENT END:VCALENDAR """) attendee1 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION;RSVP=TRUE:urn:x-uid:user02 RRULE:FREQ=DAILY TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """) organizer2 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION:urn:x-uid:user02 EXDATE:20080602T130000Z RRULE:FREQ=DAILY SUMMARY:Test END:VEVENT END:VCALENDAR """) attendee2 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION;RSVP=TRUE:urn:x-uid:user02 RRULE:FREQ=DAILY TRANSP:TRANSPARENT END:VEVENT BEGIN:VEVENT UID:12345-67890 RECURRENCE-ID:20080602T130000Z DTSTAMP:20080601T130000Z DTSTART:20080602T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=DECLINED:urn:x-uid:user02 TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """) attendee3 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION;RSVP=TRUE:urn:x-uid:user02 EXDATE:20080602T130000Z RRULE:FREQ=DAILY TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """) yield self.createOrganizerEvent("user01", organizer1) attendee = yield self.getAttendeeEvent("user02") self.assertEqual(attendee, attendee1, msg=diff_iCalStrs(attendee, attendee1)) yield self.setOrganizerEvent("user01", organizer2, run_jobs=False) yield self._runOneJob() yield self.setAttendeeEvent("user02", attendee2, run_jobs=False) yield self.setAttendeeEvent("user02", attendee3, run_jobs=False) yield self._runAllJobs() jobs = yield JobItem.all(self.transactionUnderTest()) self.assertEqual(len(jobs), 0) yield self.commit()
def test_replyBeforeOrganizerConsequentialChange(self): """ Test that the organizer and attendee see the attendee's partstat change when the organizer makes a consequential change whilst the attendee reply is in progress. """ organizer1 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION:urn:x-uid:user02 END:VEVENT END:VCALENDAR """) organizer2 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080602T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION:urn:x-uid:user02 SUMMARY:Test END:VEVENT END:VCALENDAR """) organizer3 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080602T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2;X-CALENDARSERVER-RESET-PARTSTAT=1:urn:x-uid:user02 SEQUENCE:1 SUMMARY:Test END:VEVENT END:VCALENDAR """) attendee1 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION;RSVP=TRUE:urn:x-uid:user02 TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """) attendee2 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080601T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected]:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user02 END:VEVENT END:VCALENDAR """) attendee3 = Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:12345-67890 DTSTAMP:20080601T130000Z DTSTART:20080602T130000Z DURATION:PT1H ORGANIZER;CN=User 01;[email protected];SCHEDULE-STATUS=1.2:urn:x-uid:user01 ATTENDEE;CN=User 01;[email protected];PARTSTAT=ACCEPTED:urn:x-uid:user01 ATTENDEE;CN=User 02;[email protected];PARTSTAT=NEEDS-ACTION;RSVP=TRUE:urn:x-uid:user02 SEQUENCE:1 SUMMARY:Test TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR """) yield self.createOrganizerEvent("user01", organizer1) attendee = yield self.getAttendeeEvent("user02") self.assertEqual(attendee, attendee1, msg=diff_iCalStrs(attendee, attendee1)) yield self.setOrganizerEvent("user01", organizer2, run_jobs=False) yield self._runOneJob() yield self.setAttendeeEvent("user02", attendee2, run_jobs=False) yield self._runAllJobs() jobs = yield JobItem.all(self.transactionUnderTest()) self.assertEqual(len(jobs), 0) yield self.commit() organizer = yield self.getOrganizerEvent("user01") self.assertEqual(normalize_iCalStr(organizer), normalize_iCalStr(organizer3), msg=diff_iCalStrs(organizer3, organizer)) attendee = yield self.getAttendeeEvent("user02") self.assertEqual(normalize_iCalStr(attendee), normalize_iCalStr(attendee3), msg=diff_iCalStrs(attendee3, attendee))