def test_freebusy(self): """ Test that action=component 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) fbstart = "{now:04d}0102T000000Z".format(**self.nowYear) fbend = "{now:04d}0103T000000Z".format(**self.nowYear) shared = yield self.calendarUnderTest(txn=self.theTransactionUnderTest(1), home="puser01", name="shared-calendar") fbinfo = FreebusyQuery.FBInfo([], [], []) timerange = Period(DateTime.parseText(fbstart), DateTime.parseText(fbend)) organizer = recipient = (yield calendarUserFromCalendarUserAddress("mailto:[email protected]", self.theTransactionUnderTest(1))) freebusy = FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange) matchtotal = (yield freebusy.generateFreeBusyInfo([shared, ], fbinfo)) self.assertEqual(matchtotal, 1) self.assertEqual(fbinfo[0], [Period.parseText("{now:04d}0102T140000Z/PT1H".format(**self.nowYear)), ]) self.assertEqual(len(fbinfo[1]), 0) self.assertEqual(len(fbinfo[2]), 0) yield self.commitTransaction(1)
def test_one_event(self): """ Test when the calendar is empty. """ data = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:1234-5678 DTSTAMP:20080601T000000Z DTSTART:%s DTEND:%s END:VEVENT END:VCALENDAR """ % (self.now_12H.getText(), self.now_13H.getText(),) yield self._createCalendarObject(data, "user01", "test.ics") calendar = (yield self.calendarUnderTest(home="user01", name="calendar_1")) fbinfo = [[], [], [], ] matchtotal = 0 timerange = caldavxml.TimeRange(start=self.now.getText(), end=self.now_1D.getText()) result = (yield generateFreeBusyInfo(calendar, fbinfo, timerange, matchtotal)) self.assertEqual(result, 1) self.assertEqual(fbinfo[0], [Period.parseText("%s/%s" % (self.now_12H.getText(), self.now_13H.getText(),)), ]) self.assertEqual(len(fbinfo[1]), 0) self.assertEqual(len(fbinfo[2]), 0)
def _externalGenerateFreeBusyInfo( calresource, fbinfo, timerange, matchtotal, excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False, servertoserver=False, event_details=None, logItems=None, accountingItems=None, ): """ Generate a freebusy response for an external (cross-pod) calendar by making a cross-pod call. This will bypass any type of smart caching on this pod in favor of using caching on the pod hosting the actual calendar data. See L{_internalGenerateFreeBusyInfo} for argument description. """ fbresults, matchtotal = yield calresource._txn.store( ).conduit.send_freebusy(calresource, timerange, matchtotal, excludeuid, organizer, organizerPrincipal, same_calendar_user, servertoserver, event_details) for i in range(3): fbinfo[i].extend([Period.parseText(p) for p in fbresults[i]]) returnValue(matchtotal)
def test_one_event(self): """ Test when the calendar is empty. """ data = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:1234-5678 DTSTAMP:20080601T000000Z DTSTART:%s DTEND:%s END:VEVENT END:VCALENDAR """ % (self.now_12H.getText(), self.now_13H.getText(),) yield self._createCalendarObject(data, "user01", "test.ics") calendar = (yield self.calendarUnderTest(home="user01", name="calendar_1")) fbinfo = FreebusyQuery.FBInfo([], [], []) timerange = Period(self.now, self.now_1D) organizer = recipient = yield calendarUserFromCalendarUserAddress("mailto:[email protected]", self.transactionUnderTest()) freebusy = FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange) result = (yield freebusy.generateFreeBusyInfo([calendar, ], fbinfo)) self.assertEqual(result, 1) self.assertEqual(fbinfo.busy, [Period.parseText("%s/%s" % (self.now_12H.getText(), self.now_13H.getText(),)), ]) self.assertEqual(len(fbinfo.tentative), 0) self.assertEqual(len(fbinfo.unavailable), 0)
def getCacheEntry(cls, calresource, useruid, timerange): key = str(calresource.id()) + "/" + useruid token = (yield calresource.syncToken()) entry = (yield cls.fbcacher.get(key)) if entry: # Offset one day at either end to account for floating entry_timerange = Period.parseText(entry.timerange) cached_start = entry_timerange.getStart() + Duration( days=cls.CACHE_DAYS_FLOATING_ADJUST) cached_end = entry_timerange.getEnd() - Duration( days=cls.CACHE_DAYS_FLOATING_ADJUST) # Verify that the requested time range lies within the cache time range if compareDateTime(timerange.getEnd(), cached_end) <= 0 and compareDateTime( timerange.getStart(), cached_start) >= 0: # Verify that cached entry is still valid if token == entry.token: returnValue(entry.fbresults) returnValue(None)
def recv_freebusy(self, txn, request): """ Process a freebusy cross-pod request. Message arguments as per L{send_freebusy}. @param request: request arguments @type request: C{dict} """ # Operate on the L{CommonHomeChild} calresource, _ignore = yield self._getStoreObjectForRequest( txn, request) organizer = yield calendarUserFromCalendarUserAddress( request["organizer"], txn) if request["organizer"] else None recipient = yield calendarUserFromCalendarUserAddress( request["recipient"], txn) if request["recipient"] else None freebusy = FreebusyQuery( organizer=organizer, recipient=recipient, timerange=Period.parseText(request["timerange"]), excludeUID=request["excludeuid"], event_details=request["event_details"], ) fbinfo = FreebusyQuery.FBInfo([], [], []) matchtotal = yield freebusy.generateFreeBusyInfo( [ calresource, ], fbinfo, matchtotal=request["matchtotal"], ) # Convert L{Period} objects to text for JSON response returnValue({ "fbresults": [ [item.getText() for item in fbinfo.busy], [item.getText() for item in fbinfo.tentative], [item.getText() for item in fbinfo.unavailable], ], "matchtotal": matchtotal, })
def test_one_event(self): """ Test when the calendar is empty. """ data = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:1234-5678 DTSTAMP:20080601T000000Z DTSTART:%s DTEND:%s END:VEVENT END:VCALENDAR """ % ( self.now_12H.getText(), self.now_13H.getText(), ) yield self._createCalendarObject(data, "user01", "test.ics") calendar = (yield self.calendarUnderTest(home="user01", name="calendar_1")) fbinfo = FreebusyQuery.FBInfo([], [], []) timerange = Period(self.now, self.now_1D) organizer = recipient = yield calendarUserFromCalendarUserAddress( "mailto:[email protected]", self.transactionUnderTest()) freebusy = FreebusyQuery(organizer=organizer, recipient=recipient, timerange=timerange) result = (yield freebusy.generateFreeBusyInfo([ calendar, ], fbinfo)) self.assertEqual(result, 1) self.assertEqual(fbinfo.busy, [ Period.parseText("%s/%s" % ( self.now_12H.getText(), self.now_13H.getText(), )), ]) self.assertEqual(len(fbinfo.tentative), 0) self.assertEqual(len(fbinfo.unavailable), 0)
def _externalGenerateFreeBusyInfo(self, fbset, fbinfo, matchtotal): """ Generate a freebusy response for an external (cross-pod) calendar by making a cross-pod call. This will bypass any type of smart caching on this pod in favor of using caching on the pod hosting the actual calendar data. See L{_internalGenerateFreeBusyInfo} for argument description. """ for calresource in fbset: fbresults, matchtotal = yield calresource._txn.store().conduit.send_freebusy( calresource, self.organizer.cuaddr if self.organizer else None, self.recipient.cuaddr if self.recipient else None, self.timerange, matchtotal, self.excludeuid, self.event_details, ) for i in range(3): fbinfo[i].extend([Period.parseText(p) for p in fbresults[i]]) returnValue(matchtotal)
def getCacheEntry(cls, calresource, useruid, timerange): key = str(calresource.id()) + "/" + useruid token = (yield calresource.syncToken()) entry = (yield cls.fbcacher.get(key)) if entry: # Offset one day at either end to account for floating entry_timerange = Period.parseText(entry.timerange) cached_start = entry_timerange.getStart() + Duration(days=cls.CACHE_DAYS_FLOATING_ADJUST) cached_end = entry_timerange.getEnd() - Duration(days=cls.CACHE_DAYS_FLOATING_ADJUST) # Verify that the requested time range lies within the cache time range if compareDateTime(timerange.getEnd(), cached_end) <= 0 and compareDateTime(timerange.getStart(), cached_start) >= 0: # Verify that cached entry is still valid if token == entry.token: returnValue(entry.fbresults) returnValue(None)
def _externalGenerateFreeBusyInfo( calresource, fbinfo, timerange, matchtotal, excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False, servertoserver=False, event_details=None, logItems=None, ): """ Generate a freebusy response for an external (cross-pod) calendar by making a cross-pod call. This will bypass any type of smart caching on this pod in favor of using caching on the pod hosting the actual calendar data. See L{_internalGenerateFreeBusyInfo} for argument description. """ fbresults, matchtotal = yield calresource._txn.store().conduit.send_freebusy(calresource, timerange, matchtotal, excludeuid, organizer, organizerPrincipal, same_calendar_user, servertoserver, event_details) for i in range(3): fbinfo[i].extend([Period.parseText(p) for p in fbresults[i]]) returnValue(matchtotal)
def recv_freebusy(self, txn, request): """ Process a freebusy cross-pod request. Message arguments as per L{send_freebusy}. @param request: request arguments @type request: C{dict} """ # Operate on the L{CommonHomeChild} calresource, _ignore = yield self._getStoreObjectForRequest(txn, request) organizer = yield calendarUserFromCalendarUserAddress(request["organizer"], txn) if request["organizer"] else None recipient = yield calendarUserFromCalendarUserAddress(request["recipient"], txn) if request["recipient"] else None freebusy = FreebusyQuery( organizer=organizer, recipient=recipient, timerange=Period.parseText(request["timerange"]), excludeUID=request["excludeuid"], event_details=request["event_details"], ) fbinfo = FreebusyQuery.FBInfo([], [], []) matchtotal = yield freebusy.generateFreeBusyInfo( [calresource, ], fbinfo, matchtotal=request["matchtotal"], ) # Convert L{Period} objects to text for JSON response returnValue({ "fbresults": [ [item.getText() for item in fbinfo.busy], [item.getText() for item in fbinfo.tentative], [item.getText() for item in fbinfo.unavailable], ], "matchtotal": matchtotal, })
def test_freebusy(self): """ Test that action=component works. """ yield self.createShare("user01", "puser01") calendar1 = yield self.calendarUnderTest(home="user01", name="calendar") yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)) yield self.commit() fbstart = "{now:04d}0102T000000Z".format(**self.nowYear) fbend = "{now:04d}0103T000000Z".format(**self.nowYear) shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar") fbinfo = [[], [], []] matchtotal = yield generateFreeBusyInfo( shared, fbinfo, TimeRange(start=fbstart, end=fbend), 0, excludeuid=None, organizer=None, organizerPrincipal=None, same_calendar_user=False, servertoserver=False, event_details=False, logItems=None ) self.assertEqual(matchtotal, 1) self.assertEqual(fbinfo[0], [Period.parseText("{now:04d}0102T140000Z/PT1H".format(**self.nowYear)), ]) self.assertEqual(len(fbinfo[1]), 0) self.assertEqual(len(fbinfo[2]), 0) yield self.otherCommit()
def testParseGenerate(self): for result in TestPeriod.test_data: period = Period.parseText(result) self.assertEqual(period.getText(), result)
def test_simple(self): data = ( ( "#1.1 No busy time", FreebusyQuery.FBInfo([], [], []), Period.parseText("20080601T000000Z/20080602T000000Z"), None, None, None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z END:VFREEBUSY END:VCALENDAR """, ), ( "#1.2 No busy time with organizerProp & attendeeProp", FreebusyQuery.FBInfo([], [], []), Period.parseText("20080601T000000Z/20080602T000000Z"), Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.3 With single busy time", FreebusyQuery.FBInfo([ Period.parseText("20080601T120000Z/20080601T130000Z"), ], [], []), Period.parseText("20080601T000000Z/20080602T000000Z"), Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.4 With multiple busy time", FreebusyQuery.FBInfo( [ Period.parseText("20080601T120000Z/20080601T130000Z"), Period.parseText("20080601T140000Z/20080601T150000Z"), ], [], [], ), Period.parseText("20080601T000000Z/20080602T000000Z"), Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z,20080601T140000Z/20080601T150000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.5 With multiple busy time, some overlap", FreebusyQuery.FBInfo( [ Period.parseText("20080601T120000Z/20080601T130000Z"), Period.parseText("20080601T123000Z/20080601T133000Z"), Period.parseText("20080601T140000Z/20080601T150000Z"), Period.parseText("20080601T150000Z/20080601T160000Z"), ], [], [], ), Period.parseText("20080601T000000Z/20080602T000000Z"), Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T133000Z,20080601T140000Z/20080601T160000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.6 With all busy time types", FreebusyQuery.FBInfo( [ Period.parseText("20080601T120000Z/20080601T130000Z"), Period.parseText("20080601T140000Z/20080601T150000Z"), ], [ Period.parseText("20080601T140000Z/20080601T150000Z"), ], [ Period.parseText("20080601T160000Z/20080601T170000Z"), ], ), Period.parseText("20080601T000000Z/20080602T000000Z"), Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z,20080601T140000Z/20080601T150000Z FREEBUSY;FBTYPE=BUSY-TENTATIVE:20080601T140000Z/20080601T150000Z FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20080601T160000Z/20080601T170000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.7 With single busy time and event details", FreebusyQuery.FBInfo( [ Period.parseText("20080601T120000Z/20080601T130000Z"), ], [], [], ), Period.parseText("20080601T000000Z/20080602T000000Z"), Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), [ tuple( Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:1234-5678 DTSTAMP:20080601T000000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT END:VCALENDAR """).subcomponents())[0], ], """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ) for description, fbinfo, timerange, organizerProp, attendeeProp, event_details, calendar in data: freebusy = FreebusyQuery(organizerProp=organizerProp, attendeeProp=attendeeProp, timerange=timerange, event_details=event_details) result = freebusy.buildFreeBusyResult(fbinfo) self.assertEqual(normalizeiCalendarText(str(result)), calendar.replace("\n", "\r\n"), msg=description)
def test_simple(self): data = ( ( "#1.1 No busy time", [ [], [], [], ], "20080601T000000Z", "20080602T000000Z", None, None, None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z END:VFREEBUSY END:VCALENDAR """, ), ( "#1.2 No busy time with organizer & attendee", [ [], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.3 With single busy time", [ [Period.parseText("20080601T120000Z/20080601T130000Z"), ], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.4 With multiple busy time", [ [ Period.parseText("20080601T120000Z/20080601T130000Z"), Period.parseText("20080601T140000Z/20080601T150000Z"), ], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z,20080601T140000Z/20080601T150000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.5 With multiple busy time, some overlap", [ [ Period.parseText("20080601T120000Z/20080601T130000Z"), Period.parseText("20080601T123000Z/20080601T133000Z"), Period.parseText("20080601T140000Z/20080601T150000Z"), Period.parseText("20080601T150000Z/20080601T160000Z"), ], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T133000Z,20080601T140000Z/20080601T160000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.6 With all busy time types", [ [ Period.parseText("20080601T120000Z/20080601T130000Z"), Period.parseText("20080601T140000Z/20080601T150000Z"), ], [ Period.parseText("20080601T140000Z/20080601T150000Z"), ], [ Period.parseText("20080601T160000Z/20080601T170000Z"), ], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), None, """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z,20080601T140000Z/20080601T150000Z FREEBUSY;FBTYPE=BUSY-TENTATIVE:20080601T140000Z/20080601T150000Z FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20080601T160000Z/20080601T170000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ( "#1.7 With single busy time and event details", [ [Period.parseText("20080601T120000Z/20080601T130000Z"), ], [], [], ], "20080601T000000Z", "20080602T000000Z", Property("ORGANIZER", "mailto:[email protected]"), Property("ATTENDEE", "mailto:[email protected]"), [ tuple(Component.fromString("""BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT UID:1234-5678 DTSTAMP:20080601T000000Z DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT END:VCALENDAR """).subcomponents())[0], ], """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN BEGIN:VEVENT DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT BEGIN:VFREEBUSY DTSTART:20080601T000000Z DTEND:20080602T000000Z ATTENDEE:mailto:[email protected] FREEBUSY;FBTYPE=BUSY:20080601T120000Z/20080601T130000Z ORGANIZER:mailto:[email protected] END:VFREEBUSY END:VCALENDAR """, ), ) for description, fbinfo, dtstart, dtend, organizer, attendee, event_details, calendar in data: timerange = caldavxml.TimeRange(start=dtstart, end=dtend) result = buildFreeBusyResult(fbinfo, timerange, organizer=organizer, attendee=attendee, event_details=event_details) self.assertEqual(normalizeiCalendarText(str(result)), calendar.replace("\n", "\r\n"), msg=description)