Example #1
0
    def test_vcalendar_no_effect(self):

        data = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ORGANIZER;CN=User 01:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")

        no_effect = CalendarData(CalendarComponent(name="VCALENDAR"))
        for item in (
                data,
                Component.fromString(data),
        ):
            self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)),
                             data)

        no_effect = CalendarData(
            CalendarComponent(AllComponents(),
                              AllProperties(),
                              name="VCALENDAR"))
        for item in (
                data,
                Component.fromString(data),
        ):
            self.assertEqual(str(CalendarDataFilter(no_effect).filter(item)),
                             data)
Example #2
0
    def test_vevent_some_props(self):

        data = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ORGANIZER;CN=User 01:mailto:[email protected]
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")

        result = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")

        empty = CalendarData(
            CalendarComponent(CalendarComponent(AllComponents(),
                                                Property(name="UID", ),
                                                Property(name="DTSTART", ),
                                                Property(name="DTEND", ),
                                                name="VEVENT"),
                              AllProperties(),
                              name="VCALENDAR"))

        for item in (
                data,
                Component.fromString(data),
        ):
            self.assertEqual(str(CalendarDataFilter(empty).filter(item)),
                             result)
Example #3
0
    def test_empty(self):

        data = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ORGANIZER;CN=User 01:mailto:[email protected]
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")

        empty = CalendarData()
        for item in (
                data,
                Component.fromString(data),
        ):
            self.assertEqual(str(CalendarDataFilter(empty).filter(item)), data)
Example #4
0
def _namedPropertiesForResource(request, props, resource, calendar=None, timezone=None, vcard=None, isowner=True, dataAllowed=True, forbidden=False):
    """
    Return the specified properties on the specified resource.
    @param request: the L{IRequest} for the current request.
    @param props: a list of property elements or qname tuples for the properties of interest.
    @param resource: the L{CalDAVResource} for the targeted resource.
    @param calendar: the L{Component} for the calendar for the resource. This may be None
        if the calendar has not already been read in, in which case the resource
        will be used to get the calendar if needed.
    @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
    @param vcard: the L{Component} for the vcard for the resource. This may be None
        if the vcard has not already been read in, in which case the resource
        will be used to get the vcard if needed.
    @param isowner: C{True} if the authorized principal making the request is the DAV:owner,
        C{False} otherwise.
    @param dataAllowed: C{True} if calendar/address data is allowed to be returned,
        C{False} otherwise.
    @param forbidden: if C{True} then return 403 status for all properties,
        C{False} otherwise.
    @return: a map of OK and NOT FOUND property values.
    """
    properties_by_status = {
        responsecode.OK        : [],
        responsecode.FORBIDDEN : [],
        responsecode.NOT_FOUND : [],
    }

    # Look for Prefer header first, then try Brief
    prefer = request.headers.getHeader("prefer", {})
    returnMinimal = any([key == "return" and value == "minimal" for key, value, _ignore_args in prefer])
    if not returnMinimal:
        returnMinimal = request.headers.getHeader("brief", False)

    for property in props:
        if isinstance(property, element.WebDAVElement):
            qname = property.qname()
        else:
            qname = property

        if forbidden:
            properties_by_status[responsecode.FORBIDDEN].append(propertyName(qname))
            continue

        if isinstance(property, caldavxml.CalendarData):
            if dataAllowed:
                # Handle private events access restrictions
                if calendar is None:
                    calendar = (yield resource.componentForUser())
                filtered = HiddenInstanceFilter().filter(calendar)
                filtered = PrivateEventFilter(resource.accessMode, isowner).filter(filtered)
                filtered = CalendarDataFilter(property, timezone).filter(filtered)
                propvalue = CalendarData.fromCalendar(filtered, format=property.content_type)
                properties_by_status[responsecode.OK].append(propvalue)
            else:
                properties_by_status[responsecode.FORBIDDEN].append(propertyName(qname))
            continue

        if isinstance(property, carddavxml.AddressData):
            if dataAllowed:
                if vcard is None:
                    vcard = (yield resource.vCard())
                filtered = AddressDataFilter(property).filter(vcard)
                propvalue = AddressData.fromAddress(filtered, format=property.content_type)
                properties_by_status[responsecode.OK].append(propvalue)
            else:
                properties_by_status[responsecode.FORBIDDEN].append(propertyName(qname))
            continue

        has = (yield resource.hasProperty(property, request))

        if has:
            try:
                prop = (yield resource.readProperty(property, request))
                if prop is not None:
                    properties_by_status[responsecode.OK].append(prop)
                elif not returnMinimal:
                    properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))
            except HTTPError:
                f = Failure()
                status = statusForFailure(f, "getting property: %s" % (qname,))
                if status not in properties_by_status:
                    properties_by_status[status] = []
                if not returnMinimal or status != responsecode.NOT_FOUND:
                    properties_by_status[status].append(propertyName(qname))
        elif not returnMinimal:
            properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))

    returnValue(properties_by_status)
Example #5
0
def _namedPropertiesForResource(request, props, resource, calendar=None, timezone=None, vcard=None, isowner=True):
    """
    Return the specified properties on the specified resource.
    @param request: the L{IRequest} for the current request.
    @param props: a list of property elements or qname tuples for the properties of interest.
    @param resource: the L{CalDAVResource} for the targeted resource.
    @param calendar: the L{Component} for the calendar for the resource. This may be None
        if the calendar has not already been read in, in which case the resource
        will be used to get the calendar if needed.
    @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day.
    @param vcard: the L{Component} for the vcard for the resource. This may be None
        if the vcard has not already been read in, in which case the resource
        will be used to get the vcard if needed.
    @param isowner: C{True} if the authorized principal making the request is the DAV:owner,
        C{False} otherwise.
    @return: a map of OK and NOT FOUND property values.
    """
    properties_by_status = {
        responsecode.OK        : [],
        responsecode.NOT_FOUND : [],
    }

    # Look for Prefer header first, then try Brief
    prefer = request.headers.getHeader("prefer", {})
    returnMinimal = any([key == "return" and value == "minimal" for key, value, _ignore_args in prefer])
    if not returnMinimal:
        returnMinimal = request.headers.getHeader("brief", False)

    for property in props:
        if isinstance(property, caldavxml.CalendarData):
            # Handle private events access restrictions
            if calendar is None:
                calendar = (yield resource.iCalendarForUser(request))
            filtered = HiddenInstanceFilter().filter(calendar)
            filtered = PrivateEventFilter(resource.accessMode, isowner).filter(filtered)
            filtered = CalendarDataFilter(property, timezone).filter(filtered)
            propvalue = CalendarData.fromCalendar(filtered, format=property.content_type)
            properties_by_status[responsecode.OK].append(propvalue)
            continue

        if isinstance(property, carddavxml.AddressData):
            if vcard is None:
                vcard = (yield resource.vCard())
            filtered = AddressDataFilter(property).filter(vcard)
            propvalue = AddressData.fromAddress(filtered, format=property.content_type)
            properties_by_status[responsecode.OK].append(propvalue)
            continue

        if isinstance(property, element.WebDAVElement):
            qname = property.qname()
        else:
            qname = property

        has = (yield resource.hasProperty(property, request))

        if has:
            try:
                prop = (yield resource.readProperty(property, request))
                if prop is not None:
                    properties_by_status[responsecode.OK].append(prop)
                elif not returnMinimal:
                    properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))
            except HTTPError:
                f = Failure()
                status = statusForFailure(f, "getting property: %s" % (qname,))
                if status not in properties_by_status:
                        properties_by_status[status] = []
                if not returnMinimal or status != responsecode.NOT_FOUND:
                    properties_by_status[status].append(propertyName(qname))
        elif not returnMinimal:
            properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))

    returnValue(properties_by_status)
    def filter(self, ical):
        """
        Filter the supplied iCalendar object using the request information.

        @param ical: iCalendar object
        @type ical: L{Component} or C{str}

        @return: L{Component} for the filtered calendar data
        """

        if self.isowner or self.accessRestriction == Component.ACCESS_PUBLIC or not self.accessRestriction:
            # No need to filter for the owner or public event
            return ical

        elif self.accessRestriction == Component.ACCESS_PRIVATE:
            # We should never get here because ACCESS_PRIVATE is protected via an ACL
            raise HTTPError(
                StatusResponse(responsecode.FORBIDDEN, "Access Denied"))

        elif self.accessRestriction == Component.ACCESS_PUBLIC:
            return ical
        elif self.accessRestriction in (Component.ACCESS_CONFIDENTIAL,
                                        Component.ACCESS_RESTRICTED):
            # Create a CALDAV:calendar-data element with the appropriate iCalendar Component/Property
            # filter in place for the access restriction in use

            extra_access = ()
            if self.accessRestriction == Component.ACCESS_RESTRICTED:
                extra_access = (
                    Property(name="SUMMARY"),
                    Property(name="LOCATION"),
                )

            calendardata = CalendarData(
                CalendarComponent(

                    # VCALENDAR properties
                    Property(name="PRODID"),
                    Property(name="VERSION"),
                    Property(name="CALSCALE"),
                    Property(name=Component.ACCESS_PROPERTY),

                    # VEVENT
                    CalendarComponent(Property(name="UID"),
                                      Property(name="RECURRENCE-ID"),
                                      Property(name="SEQUENCE"),
                                      Property(name="DTSTAMP"),
                                      Property(name="STATUS"),
                                      Property(name="TRANSP"),
                                      Property(name="DTSTART"),
                                      Property(name="DTEND"),
                                      Property(name="DURATION"),
                                      Property(name="RRULE"),
                                      Property(name="RDATE"),
                                      Property(name="EXRULE"),
                                      Property(name="EXDATE"), *extra_access,
                                      **{"name": "VEVENT"}),

                    # VTODO
                    CalendarComponent(Property(name="UID"),
                                      Property(name="RECURRENCE-ID"),
                                      Property(name="SEQUENCE"),
                                      Property(name="DTSTAMP"),
                                      Property(name="STATUS"),
                                      Property(name="DTSTART"),
                                      Property(name="COMPLETED"),
                                      Property(name="DUE"),
                                      Property(name="DURATION"),
                                      Property(name="RRULE"),
                                      Property(name="RDATE"),
                                      Property(name="EXRULE"),
                                      Property(name="EXDATE"), *extra_access,
                                      **{"name": "VTODO"}),

                    # VJOURNAL
                    CalendarComponent(Property(name="UID"),
                                      Property(name="RECURRENCE-ID"),
                                      Property(name="SEQUENCE"),
                                      Property(name="DTSTAMP"),
                                      Property(name="STATUS"),
                                      Property(name="TRANSP"),
                                      Property(name="DTSTART"),
                                      Property(name="RRULE"),
                                      Property(name="RDATE"),
                                      Property(name="EXRULE"),
                                      Property(name="EXDATE"), *extra_access,
                                      **{"name": "VJOURNAL"}),

                    # VFREEBUSY
                    CalendarComponent(Property(name="UID"),
                                      Property(name="DTSTAMP"),
                                      Property(name="DTSTART"),
                                      Property(name="DTEND"),
                                      Property(name="DURATION"),
                                      Property(name="FREEBUSY"), *extra_access,
                                      **{"name": "VFREEBUSY"}),

                    # VTIMEZONE
                    CalendarComponent(
                        AllProperties(),
                        AllComponents(),
                        name="VTIMEZONE",
                    ),
                    name="VCALENDAR",
                ), )

            # Now "filter" the resource calendar data through the CALDAV:calendar-data element
            return CalendarDataFilter(calendardata).filter(ical)
        else:
            # Unknown access restriction
            raise HTTPError(
                StatusResponse(responsecode.FORBIDDEN, "Access Denied"))
Example #7
0
    def test_CalendarDataTextAndJSON(self):
        """
        Text that we can both parse and generate CalendarData elements with both text and json formats.
        """
        dataText = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
DTSTAMP:20080601T120000Z
EXDATE:20080602T120000Z
EXDATE:20080603T120000Z
ORGANIZER;CN=User 01:mailto:[email protected]
RRULE:FREQ=DAILY;COUNT=400
SUMMARY:Test
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")

        dataXML = """<?xml version='1.0' encoding='UTF-8'?>
<calendar-data xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[%s]]></calendar-data>""" % (
            dataText, )

        jsonText = """[
  "vcalendar",
  [
    ["version", {}, "text", "2.0"],
    ["prodid", {}, "text", "-//CALENDARSERVER.ORG//NONSGML Version 1//EN"]
  ],
  [
    ["vevent",
      [
        ["uid", {}, "text", "12345-67890"],
        ["dtstart", {}, "date-time", "2008-06-01T12:00:00Z"],
        ["dtend", {}, "date-time", "2008-06-01T13:00:00Z"],
        ["attendee", {}, "cal-address", "mailto:[email protected]"],
        ["attendee", {}, "cal-address", "mailto:[email protected]"],
        ["dtstamp", {}, "date-time", "2008-06-01T12:00:00Z"],
        ["exdate", {}, "date-time", "2008-06-02T12:00:00Z"],
        ["exdate", {}, "date-time", "2008-06-03T12:00:00Z"],
        ["organizer", {"cn": "User 01"}, "cal-address", "mailto:[email protected]"],
        ["rrule", {}, "recur", {"count": 400, "freq": "DAILY"}],
        ["summary", {}, "text", "Test"]
      ],
      [
      ]
    ]
  ]
]
"""

        jsonXML = """<?xml version='1.0' encoding='UTF-8'?>
<calendar-data content-type='application/calendar+json' xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[%s]]></calendar-data>""" % (
            jsonText, )

        cd = CalendarData.fromTextData(dataText)
        self.assertEqual(
            normalize_iCalStr(cd.calendar().getTextWithTimezones(
                True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(
            normalizeJSON(cd.calendar().getTextWithTimezones(
                True, format="application/calendar+json")),
            normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "text/calendar")
        self.assertEqual(cd.toxml(), dataXML)

        comp = Component.fromString(dataText)
        cd = CalendarData.fromCalendar(comp)
        self.assertEqual(
            normalize_iCalStr(cd.calendar().getTextWithTimezones(
                True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(
            normalizeJSON(cd.calendar().getTextWithTimezones(
                True, format="application/calendar+json")),
            normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "text/calendar")
        self.assertEqual(cd.toxml(), dataXML)

        cd = CalendarData.fromCalendar(comp,
                                       format="application/calendar+json")
        self.assertEqual(
            normalize_iCalStr(cd.calendar().getTextWithTimezones(
                True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(
            normalizeJSON(cd.calendar().getTextWithTimezones(
                True, format="application/calendar+json")),
            normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "application/calendar+json")
        self.assertEqual(normalizeJSON(cd.toxml()), normalizeJSON(jsonXML))

        cd = CalendarData.fromTextData(jsonText,
                                       format="application/calendar+json")
        self.assertEqual(
            normalize_iCalStr(cd.calendar().getTextWithTimezones(
                True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(
            normalizeJSON(cd.calendar().getTextWithTimezones(
                True, format="application/calendar+json")),
            normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "application/calendar+json")
        self.assertEqual(cd.toxml(), jsonXML)

        comp = Component.fromString(jsonText,
                                    format="application/calendar+json")
        cd = CalendarData.fromCalendar(comp)
        self.assertEqual(
            normalize_iCalStr(cd.calendar().getTextWithTimezones(
                True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(
            normalizeJSON(cd.calendar().getTextWithTimezones(
                True, format="application/calendar+json")),
            normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "text/calendar")
        self.assertEqual(cd.toxml(), dataXML)

        cd = CalendarData.fromCalendar(comp,
                                       format="application/calendar+json")
        self.assertEqual(
            normalize_iCalStr(cd.calendar().getTextWithTimezones(
                True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(
            normalizeJSON(cd.calendar().getTextWithTimezones(
                True, format="application/calendar+json")),
            normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "application/calendar+json")
        self.assertEqual(normalizeJSON(cd.toxml()), normalizeJSON(jsonXML))
    def test_CalendarDataTextAndJSON(self):
        """
        Text that we can both parse and generate CalendarData elements with both text and json formats.
        """
        dataText = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
DTSTAMP:20080601T120000Z
EXDATE:20080602T120000Z
EXDATE:20080603T120000Z
ORGANIZER;CN=User 01:mailto:[email protected]
RRULE:FREQ=DAILY
SUMMARY:Test
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")

        dataXML = """<?xml version='1.0' encoding='UTF-8'?>
<calendar-data xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[%s]]></calendar-data>""" % (dataText,)

        jsonText = """[
  "vcalendar",
  [
    ["version", {}, "text", "2.0"],
    ["prodid", {}, "text", "-//CALENDARSERVER.ORG//NONSGML Version 1//EN"]
  ],
  [
    ["vevent",
      [
        ["uid", {}, "text", "12345-67890"],
        ["dtstart", {}, "date-time", "2008-06-01T12:00:00Z"],
        ["dtend", {}, "date-time", "2008-06-01T13:00:00Z"],
        ["attendee", {}, "cal-address", "mailto:[email protected]"],
        ["attendee", {}, "cal-address", "mailto:[email protected]"],
        ["dtstamp", {}, "date-time", "2008-06-01T12:00:00Z"],
        ["exdate", {}, "date-time", "2008-06-02T12:00:00Z"],
        ["exdate", {}, "date-time", "2008-06-03T12:00:00Z"],
        ["organizer", {"cn": "User 01"}, "cal-address", "mailto:[email protected]"],
        ["rrule", {}, "recur", {"freq": "DAILY"}],
        ["summary", {}, "text", "Test"]
      ],
      [
      ]
    ]
  ]
]
"""

        jsonXML = """<?xml version='1.0' encoding='UTF-8'?>
<calendar-data content-type='application/calendar+json' xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[%s]]></calendar-data>""" % (jsonText,)

        cd = CalendarData.fromTextData(dataText)
        self.assertEqual(normalize_iCalStr(cd.calendar().getTextWithTimezones(True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(normalizeJSON(cd.calendar().getTextWithTimezones(True, format="application/calendar+json")), normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "text/calendar")
        self.assertEqual(cd.toxml(), dataXML)

        comp = Component.fromString(dataText)
        cd = CalendarData.fromCalendar(comp)
        self.assertEqual(normalize_iCalStr(cd.calendar().getTextWithTimezones(True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(normalizeJSON(cd.calendar().getTextWithTimezones(True, format="application/calendar+json")), normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "text/calendar")
        self.assertEqual(cd.toxml(), dataXML)

        cd = CalendarData.fromCalendar(comp, format="application/calendar+json")
        self.assertEqual(normalize_iCalStr(cd.calendar().getTextWithTimezones(True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(normalizeJSON(cd.calendar().getTextWithTimezones(True, format="application/calendar+json")), normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "application/calendar+json")
        self.assertEqual(normalizeJSON(cd.toxml()), normalizeJSON(jsonXML))

        cd = CalendarData.fromTextData(jsonText, format="application/calendar+json")
        self.assertEqual(normalize_iCalStr(cd.calendar().getTextWithTimezones(True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(normalizeJSON(cd.calendar().getTextWithTimezones(True, format="application/calendar+json")), normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "application/calendar+json")
        self.assertEqual(cd.toxml(), jsonXML)

        comp = Component.fromString(jsonText, format="application/calendar+json")
        cd = CalendarData.fromCalendar(comp)
        self.assertEqual(normalize_iCalStr(cd.calendar().getTextWithTimezones(True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(normalizeJSON(cd.calendar().getTextWithTimezones(True, format="application/calendar+json")), normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "text/calendar")
        self.assertEqual(cd.toxml(), dataXML)

        cd = CalendarData.fromCalendar(comp, format="application/calendar+json")
        self.assertEqual(normalize_iCalStr(cd.calendar().getTextWithTimezones(True, format="text/calendar")), normalize_iCalStr(dataText))
        self.assertEqual(normalizeJSON(cd.calendar().getTextWithTimezones(True, format="application/calendar+json")), normalizeJSON(jsonText))
        self.assertEqual(cd.content_type, "application/calendar+json")
        self.assertEqual(normalizeJSON(cd.toxml()), normalizeJSON(jsonXML))