|
| 1 | +"""Property parameters on URI-typed properties must survive a jCal round-trip. |
| 2 | +
|
| 3 | +``vUri.from_jcal`` used to pass the parsed parameters as the second *positional* |
| 4 | +argument. Unlike every other value type, ``vUri.__new__`` takes an ``encoding`` |
| 5 | +argument in that slot (``vUri(value, encoding, /, params=None)``), so the |
| 6 | +:class:`~icalendar.parser.Parameters` object was bound to ``encoding`` and |
| 7 | +silently discarded, leaving ``params`` at its default of ``None``. |
| 8 | +
|
| 9 | +As a result parameters such as ``FMTTYPE`` (ATTACH), ``FEATURE``/``LABEL`` |
| 10 | +(CONFERENCE) or ``RELTYPE`` (RELATED-TO) were dropped whenever a URI property was |
| 11 | +read back from jCal, breaking the round-trip required by :rfc:`7265`. |
| 12 | +""" |
| 13 | + |
| 14 | +from icalendar import Calendar, Component, Event, vUri |
| 15 | +from icalendar.parser import Parameters |
| 16 | + |
| 17 | + |
| 18 | +def _jcal_round_trip(component: Component) -> Component: |
| 19 | + return Component.from_jcal(component.to_jcal()) |
| 20 | + |
| 21 | + |
| 22 | +def test_vuri_from_jcal_keeps_parameters(): |
| 23 | + """The root cause: ``vUri.from_jcal`` must attach the parsed parameters.""" |
| 24 | + jcal_property = ["attach", {"fmttype": "text/plain"}, "uri", "http://example.com"] |
| 25 | + uri = vUri.from_jcal(jcal_property) |
| 26 | + assert uri.params == Parameters({"FMTTYPE": "text/plain"}) |
| 27 | + # The value (and the ``encoding`` slot the parameters used to be bound to) |
| 28 | + # is still parsed correctly. |
| 29 | + assert str(uri) == "http://example.com" |
| 30 | + |
| 31 | + |
| 32 | +def test_attach_fmttype_survives_jcal_round_trip(): |
| 33 | + event = Event() |
| 34 | + event.add("UID", "1") |
| 35 | + event.add( |
| 36 | + "ATTACH", |
| 37 | + vUri("http://example.com/report.txt", params={"FMTTYPE": "text/plain"}), |
| 38 | + ) |
| 39 | + restored = _jcal_round_trip(event) |
| 40 | + assert restored["ATTACH"].params["FMTTYPE"] == "text/plain" |
| 41 | + assert b"ATTACH;FMTTYPE=text/plain:" in restored.to_ical() |
| 42 | + |
| 43 | + |
| 44 | +def test_conference_multiple_parameters_survive_jcal_round_trip(): |
| 45 | + """Several parameters on the same URI property are all preserved.""" |
| 46 | + event = Event() |
| 47 | + event.add("UID", "1") |
| 48 | + event.add( |
| 49 | + "CONFERENCE", |
| 50 | + vUri("tel:+1-555-0100", params={"FEATURE": "AUDIO", "LABEL": "Call in"}), |
| 51 | + ) |
| 52 | + restored = _jcal_round_trip(event) |
| 53 | + params = restored["CONFERENCE"].params |
| 54 | + assert params["FEATURE"] == "AUDIO" |
| 55 | + assert params["LABEL"] == "Call in" |
| 56 | + |
| 57 | + |
| 58 | +def test_jcal_round_trip_is_idempotent_for_uri_parameters(): |
| 59 | + """``to_jcal`` of the reparsed component equals the original jCal.""" |
| 60 | + calendar = Calendar() |
| 61 | + calendar.add("VERSION", "2.0") |
| 62 | + calendar.add("PRODID", "-//test//EN") |
| 63 | + event = Event() |
| 64 | + event.add("UID", "1") |
| 65 | + event.add( |
| 66 | + "ATTACH", vUri("http://example.com/x.txt", params={"FMTTYPE": "text/plain"}) |
| 67 | + ) |
| 68 | + event.add( |
| 69 | + "CONFERENCE", vUri("https://chat.example.com/", params={"FEATURE": "VIDEO"}) |
| 70 | + ) |
| 71 | + calendar.add_component(event) |
| 72 | + |
| 73 | + jcal = calendar.to_jcal() |
| 74 | + assert Component.from_jcal(jcal).to_jcal() == jcal |
0 commit comments