Exemple #1
0
    def doIt(self, txn):

        uid = raw_input("Owner UID/Name: ")
        start = raw_input("Start Time (UTC YYYYMMDDTHHMMSSZ or YYYYMMDD): ")
        if len(start) == 8:
            start += "T000000Z"
        end = raw_input("End Time (UTC YYYYMMDDTHHMMSSZ or YYYYMMDD): ")
        if len(end) == 8:
            end += "T000000Z"

        try:
            start = DateTime.parseText(start)
        except ValueError:
            print("Invalid start value")
            returnValue(None)
        try:
            end = DateTime.parseText(end)
        except ValueError:
            print("Invalid end value")
            returnValue(None)
        timerange = caldavxml.TimeRange(start=start.getText(),
                                        end=end.getText())

        home = yield txn.calendarHomeWithUID(uid)
        if home is None:
            print("Could not find calendar home")
            returnValue(None)

        yield self.eventsForEachCalendar(home, uid, timerange)
    def test_query_extended(self):
        """
        Basic query test with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(**{
                                "start": "20060605T160000Z",
                            })
                        ], **{"name": ("VEVENT")}),
                    caldavxml.ComponentFilter(**{"name": ("VTODO")}),
                ], **{
                    "name": "VCALENDAR",
                    "test": "anyof"
                }))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
        self.assertEqual(len(f.child.filters), 2)
        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
Exemple #3
0
    def test_calendar_query_bogus_timezone_id(self):
        """
        Partial retrieval of events by time range.
        (CalDAV-access-09, section 7.6.1)
        """
        TimezoneCache.create()
        self.addCleanup(TimezoneCache.clear)

        calendar_properties = (
            davxml.GETETag(),
            caldavxml.CalendarData(),
        )

        query_timerange = caldavxml.TimeRange(
            start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ),
            end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ),
        )

        query = caldavxml.CalendarQuery(
            davxml.PropertyContainer(*calendar_properties),
            caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        query_timerange,
                        name="VEVENT",
                    ),
                    name="VCALENDAR",
                ), ),
            caldavxml.TimeZoneID.fromString("bogus"),
        )

        result = yield self.calendar_query(
            query, got_xml=None, expected_code=responsecode.FORBIDDEN)
        self.assertTrue("valid-timezone" in result)
Exemple #4
0
    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 test_timerange_query(self):
        """
        Basic query test with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(
                                **{
                                    "start": "20060605T160000Z",
                                    "end": "20060605T170000Z"
                                })
                        ],
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))
        j = filter.serialize()
        self.assertEqual(j["type"], "Filter")

        f = FilterBase.deserialize(j)
        self.assertTrue(isinstance(f, Filter))
        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
        self.assertTrue(
            isinstance(f.child.filters[0].qualifier.tzinfo, Timezone))
        self.assertEqual(f.child.filters[0].qualifier.tzinfo.getTimezoneID(),
                         "America/New_York")
Exemple #6
0
    def checkForFreeBusy(self):
        if not hasattr(self, "isfreebusy"):
            if (self.calendar.propertyValue("METHOD") == "REQUEST") and (self.calendar.mainType() == "VFREEBUSY"):
                # Extract time range from VFREEBUSY object
                vfreebusies = [v for v in self.calendar.subcomponents() if v.name() == "VFREEBUSY"]
                if len(vfreebusies) != 1:
                    log.error(
                        "iTIP data is not valid for a VFREEBUSY request: {cal}",
                        cal=str(self.calendar),
                    )
                    raise HTTPError(self.errorResponse(
                        responsecode.FORBIDDEN,
                        self.errorElements["invalid-scheduling-message"],
                        "iTIP data is not valid for a VFREEBUSY request",
                    ))
                dtstart = vfreebusies[0].getStartDateUTC()
                dtend = vfreebusies[0].getEndDateUTC()
                if dtstart is None or dtend is None:
                    log.error(
                        "VFREEBUSY start/end not valid: {cal}",
                        cal=str(self.calendar),
                    )
                    raise HTTPError(self.errorResponse(
                        responsecode.FORBIDDEN,
                        self.errorElements["invalid-scheduling-message"],
                        "VFREEBUSY start/end not valid",
                    ))

                # Some clients send floating instead of UTC - coerce to UTC
                if not dtstart.utc() or not dtend.utc():
                    log.error(
                        "VFREEBUSY start or end not UTC: {cal}",
                        cal=self.calendar,
                    )
                    raise HTTPError(self.errorResponse(
                        responsecode.FORBIDDEN,
                        self.errorElements["invalid-scheduling-message"],
                        "VFREEBUSY start or end not UTC",
                    ))

                self.timeRange = caldavxml.TimeRange(start=dtstart.getText(), end=dtend.getText())
                self.timeRange.start = dtstart
                self.timeRange.end = dtend

                # Look for masked UID
                self.excludeUID = self.calendar.getMaskUID()

                # Do free busy operation
                self.isfreebusy = True
            else:
                # Do regular invite (fan-out)
                self.isfreebusy = False

        return self.isfreebusy
    def simple_free_busy_query(self, cal_uri, start, end):

        query_timerange = caldavxml.TimeRange(
            start=start,
            end=end,
        )

        query = caldavxml.FreeBusyQuery(query_timerange,)

        def got_calendar(calendar):
            pass

        return self.free_busy_query(cal_uri, query, got_calendar)
Exemple #8
0
    def test_no_events(self):
        """
        Test when the calendar is empty.
        """

        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, 0)
        self.assertEqual(len(fbinfo[0]), 0)
        self.assertEqual(len(fbinfo[1]), 0)
        self.assertEqual(len(fbinfo[2]), 0)
Exemple #9
0
    def test_calendar_query_timezone(self):
        """
        Partial retrieval of events by time range.
        (CalDAV-access-09, section 7.6.1)
        """
        TimezoneCache.create()
        self.addCleanup(TimezoneCache.clear)

        tzid1 = "Etc/GMT+1"
        tz1 = Component(None, pycalendar=readVTZ(tzid1))

        calendar_properties = (
            davxml.GETETag(),
            caldavxml.CalendarData(),
        )

        query_timerange = caldavxml.TimeRange(
            start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ),
            end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ),
        )

        query = caldavxml.CalendarQuery(
            davxml.PropertyContainer(*calendar_properties),
            caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        query_timerange,
                        name="VEVENT",
                    ),
                    name="VCALENDAR",
                ), ),
            caldavxml.TimeZone.fromCalendar(tz1),
        )

        def got_xml(doc):
            if not isinstance(doc.root_element, davxml.MultiStatus):
                self.fail(
                    "REPORT response XML root element is not multistatus: %r" %
                    (doc.root_element, ))

        return self.calendar_query(query, got_xml)
    def test_query_freebusy(self):
        """
        Basic query test - with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(
                                **{
                                    "start": "20060605T160000Z",
                                    "end": "20060605T170000Z"
                                })
                        ],
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234, "user01", True)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE, ORGANIZER, FLOATING, coalesce(ADJUSTED_START_DATE, START_DATE), coalesce(ADJUSTED_END_DATE, END_DATE), FBTYPE, TIME_RANGE.TRANSPARENT, PERUSER.TRANSPARENT from CALENDAR_OBJECT, TIME_RANGE left outer join PERUSER on INSTANCE_ID = TIME_RANGE_INSTANCE_ID and USER_ID = ? where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and coalesce(ADJUSTED_START_DATE, START_DATE) < ? and coalesce(ADJUSTED_END_DATE, END_DATE) > ? or FLOATING = ? and coalesce(ADJUSTED_START_DATE, START_DATE) < ? and coalesce(ADJUSTED_END_DATE, END_DATE) > ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?",
                [
                    'user01',
                    Parameter('arg1', 3), False,
                    datetime.datetime(2006, 6, 5, 17, 0),
                    datetime.datetime(2006, 6, 5, 16, 0), True,
                    datetime.datetime(2006, 6, 5, 13, 0),
                    datetime.datetime(2006, 6, 5, 12, 0), 1234
                ]))
        self.assertEqual(args,
                         {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
        self.assertEqual(usedtimerange, True)
    def test_query_timerange(self):
        """
        Basic query test - with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(
                                **{
                                    "start": "20060605T160000Z",
                                    "end": "20060605T170000Z"
                                })
                        ],
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE < ? and END_DATE > ? or FLOATING = ? and START_DATE < ? and END_DATE > ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?",
                [
                    Parameter('arg1', 3), False,
                    datetime.datetime(2006, 6, 5, 17, 0),
                    datetime.datetime(2006, 6, 5, 16, 0), True,
                    datetime.datetime(2006, 6, 5, 13, 0),
                    datetime.datetime(2006, 6, 5, 12, 0), 1234
                ]))
        self.assertEqual(args,
                         {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
        self.assertEqual(usedtimerange, True)
Exemple #12
0
    def test_calendar_query_wrong_timezone_elements(self):
        """
        Partial retrieval of events by time range.
        (CalDAV-access-09, section 7.6.1)
        """
        TimezoneCache.create()
        self.addCleanup(TimezoneCache.clear)

        tzid1 = "Etc/GMT+1"
        tz1 = Component(None, pycalendar=readVTZ(tzid1))

        calendar_properties = (
            davxml.GETETag(),
            caldavxml.CalendarData(),
        )

        query_timerange = caldavxml.TimeRange(
            start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ),
            end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ),
        )

        query = caldavxml.CalendarQuery(
            davxml.PropertyContainer(*calendar_properties),
            caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        query_timerange,
                        name="VEVENT",
                    ),
                    name="VCALENDAR",
                ), ),
            caldavxml.TimeZone.fromCalendar(tz1),
        )
        query.children += (caldavxml.TimeZoneID.fromString(tzid1), )

        result = yield self.calendar_query(
            query, got_xml=None, expected_code=responsecode.BAD_REQUEST)
        self.assertTrue("Only one of" in result)
Exemple #13
0
    def test_query_extended(self):
        """
        Extended query test - two terms with anyof
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(**{
                                "start": "20060605T160000Z",
                            })
                        ], **{"name": ("VEVENT")}),
                    caldavxml.ComponentFilter(**{"name": ("VTODO")}),
                ], **{
                    "name": "VCALENDAR",
                    "test": "anyof"
                }))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where (ICALENDAR_TYPE = ? and (FLOATING = ? and END_DATE > ? or FLOATING = ? and END_DATE > ?) or ICALENDAR_TYPE = ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?",
                [
                    'VEVENT', False,
                    datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True,
                    datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()),
                    'VTODO', 1234
                ]))
        self.assertEqual(args, {})
        self.assertEqual(usedtimerange, True)
Exemple #14
0
    def test_calendar_query_time_range(self):
        """
        Partial retrieval of events by time range.
        (CalDAV-access-09, section 7.6.1)
        """
        calendar_properties = (
            davxml.GETETag(),
            caldavxml.CalendarData(
                caldavxml.CalendarComponent(
                    caldavxml.AllProperties(),
                    caldavxml.CalendarComponent(
                        caldavxml.Property(name="X-ABC-GUID"),
                        caldavxml.Property(name="UID"),
                        caldavxml.Property(name="DTSTART"),
                        caldavxml.Property(name="DTEND"),
                        caldavxml.Property(name="DURATION"),
                        caldavxml.Property(name="EXDATE"),
                        caldavxml.Property(name="EXRULE"),
                        caldavxml.Property(name="RDATE"),
                        caldavxml.Property(name="RRULE"),
                        caldavxml.Property(name="LOCATION"),
                        caldavxml.Property(name="SUMMARY"),
                        name="VEVENT",
                    ),
                    caldavxml.CalendarComponent(
                        caldavxml.AllProperties(),
                        caldavxml.AllComponents(),
                        name="VTIMEZONE",
                    ),
                    name="VCALENDAR",
                ), ),
        )

        query_timerange = caldavxml.TimeRange(
            start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ),
            end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ),
        )

        query = caldavxml.CalendarQuery(
            davxml.PropertyContainer(*calendar_properties),
            caldavxml.Filter(
                caldavxml.ComponentFilter(
                    caldavxml.ComponentFilter(
                        query_timerange,
                        name="VEVENT",
                    ),
                    name="VCALENDAR",
                ), ),
        )

        def got_xml(doc):
            if not isinstance(doc.root_element, davxml.MultiStatus):
                self.fail(
                    "REPORT response XML root element is not multistatus: %r" %
                    (doc.root_element, ))

            for response in doc.root_element.childrenOfType(
                    davxml.PropertyStatusResponse):
                properties_to_find = [p.qname() for p in calendar_properties]

                for propstat in response.childrenOfType(davxml.PropertyStatus):
                    status = propstat.childOfType(davxml.Status)
                    properties = propstat.childOfType(
                        davxml.PropertyContainer).children

                    if status.code != responsecode.OK:
                        self.fail(
                            "REPORT failed (status %s) to locate properties: %r"
                            % (status.code, properties))

                    for property in properties:
                        qname = property.qname()
                        if qname in properties_to_find:
                            properties_to_find.remove(qname)
                        else:
                            self.fail(
                                "REPORT found property we didn't ask for: %r" %
                                (property, ))

                        if isinstance(property, caldavxml.CalendarData):
                            cal = property.calendar()
                            instances = cal.expandTimeRanges(
                                query_timerange.end)
                            vevents = [
                                x for x in cal.subcomponents()
                                if x.name() == "VEVENT"
                            ]
                            if not TimeRange(query_timerange).matchinstance(
                                    vevents[0], instances):
                                self.fail(
                                    "REPORT property %r returned calendar %s outside of request time range %r"
                                    % (property, property.calendar,
                                       query_timerange))

        return self.calendar_query(query, got_xml)
Exemple #15
0
    def _cancelEvents(self, txn, uid, cuas):

        # Anything in the past is left alone
        whenString = self.when.getText()
        query_filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                caldavxml.ComponentFilter(
                    caldavxml.TimeRange(start=whenString,),
                    name=("VEVENT",),
                ),
                name="VCALENDAR",
            )
        )
        query_filter = Filter(query_filter)

        count = 0
        txn = self.store.newTransaction()
        storeCalHome = yield txn.calendarHomeWithUID(uid)
        calendarNames = yield storeCalHome.listCalendars()
        yield txn.commit()

        for calendarName in calendarNames:

            txn = self.store.newTransaction(authz_uid=uid)
            storeCalHome = yield txn.calendarHomeWithUID(uid)
            calendar = yield storeCalHome.calendarWithName(calendarName)
            allChildNames = []
            futureChildNames = set()

            # Only purge owned calendars
            if calendar.owned():
                # all events
                for childName in (yield calendar.listCalendarObjects()):
                    allChildNames.append(childName)

                # events matching filter
                for childName, _ignore_childUid, _ignore_childType in (yield calendar.search(query_filter)):
                    futureChildNames.add(childName)

            yield txn.commit()

            for childName in allChildNames:

                txn = self.store.newTransaction(authz_uid=uid)
                storeCalHome = yield txn.calendarHomeWithUID(uid)
                calendar = yield storeCalHome.calendarWithName(calendarName)
                doScheduling = childName in futureChildNames

                try:
                    childResource = yield calendar.calendarObjectWithName(childName)

                    uri = "/calendars/__uids__/%s/%s/%s" % (storeCalHome.uid(), calendar.name(), childName)
                    incrementCount = self.dryrun
                    if self.verbose:
                        if self.dryrun:
                            print("Would delete%s: %s" % (" with scheduling" if doScheduling else "", uri,))
                        else:
                            print("Deleting%s: %s" % (" with scheduling" if doScheduling else "", uri,))
                    if not self.dryrun:
                        retry = False
                        try:
                            yield childResource.purge(implicitly=doScheduling)
                            incrementCount = True
                        except Exception, e:
                            print("Exception deleting %s: %s" % (uri, str(e)))
                            retry = True

                        if retry and doScheduling:
                            # Try again with implicit scheduling off
                            print("Retrying deletion of %s with scheduling turned off" % (uri,))
                            try:
                                yield childResource.purge(implicitly=False)
                                incrementCount = True
                            except Exception, e:
                                print("Still couldn't delete %s even with scheduling turned off: %s" % (uri, str(e)))

                    if incrementCount:
                        count += 1

                    # Commit
                    yield txn.commit()

                except Exception, e:
                    # Abort
                    yield txn.abort()
                    raise e
Exemple #16
0
    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)