|
1 | 1 | # fmt: off |
2 | | -"""This text was updated 2025-05-17. The plan is to reorganize this |
3 | | -file a lot over the next few months, see |
4 | | -https://github.com/python-caldav/caldav/issues/402 |
5 | | -
|
| 2 | +""" |
6 | 3 | This file serves as a database of different compatibility issues we've |
7 | 4 | encountered while working on the caldav library, and descriptions on |
8 | 5 | how the well-known servers behave. |
| 6 | +""" |
9 | 7 |
|
10 | | -As for now, this is a list of binary "flags" that could be turned on |
11 | | -or off. My experience is that there are often neuances, so the |
12 | | -compatibility matrix will be changed from being a list of flags to a |
13 | | -key=value store in the near future (at least, that's the plan). |
| 8 | +## NEW STYLE |
| 9 | + |
| 10 | +## (we're gradually moving stuff from the good old |
| 11 | +## "incompatibility_description" below over to |
| 12 | +## "compatibility_features") |
| 13 | + |
| 14 | +class FeatureSet: |
| 15 | + """Work in progress ... TODO: write a better class description. |
| 16 | +
|
| 17 | + This class holds the description of different behaviour observed in |
| 18 | + a class constant. |
| 19 | +
|
| 20 | + An object of this class describes the feature set of a server. |
| 21 | +
|
| 22 | + TODO: use enums? |
| 23 | + type -> "client-feature", "server-peculiarity", "server-feature" (last is default) |
| 24 | + support -> "supported" (default), "unsupported", "fragile", "broken", "ungraceful" |
| 25 | + """ |
| 26 | + FEATURES = { |
| 27 | + "rate-limit": { |
| 28 | + "type": "client-feature", |
| 29 | + "description": "client (or test code) must not send requests too fast", |
| 30 | + "extra_keys": { |
| 31 | + "interval": "Rate limiting window, in seconds", |
| 32 | + "count": "Max number of requests to send within the interval", |
| 33 | + }}, |
| 34 | + "search-cache": { |
| 35 | + "type": "server-peculiarity", |
| 36 | + "description": "The server delivers search results from a cache which is not immediately updated when an object is changed. Hence recent changes may not be reflected in search results", |
| 37 | + "extra_keys": { |
| 38 | + "delay": "after this number of seconds, we may be reasonably sure that the search results are updated", |
| 39 | + } |
| 40 | + }, |
| 41 | + "tests-cleanup-calendar": { |
| 42 | + "type": "tests-behaviour", |
| 43 | + "description": "Deleting a calendar does not delete the objects, or perhaps create/delete of calendars does not work at all. For each test run, every calendar resource object should be deleted for every test run", |
| 44 | + }, |
| 45 | + "delete-calendar": { |
| 46 | + "description": "RFC4791 says nothing about deletion of calendars, so the server implementation is free to choose weather this should be supported or not. Section 3.2.3.2 in RFC 6638 says that if a calendar is deleted, all the calendarobjectresources on the calendar should also be deleted - but it's a bit unclear if this only applies to scheduling objects or not. Some calendar servers moves the object to a trashcan rather than deleting it" |
| 47 | + }, |
| 48 | + "recurrences": { |
| 49 | + "description": "Support for recurring events and tasks", |
| 50 | + "features": { |
| 51 | + "save_load": { |
| 52 | + "description": "Calendar server should accept ojects with RRULE given, as well as objects with recurrence sets, and should be able to deliver back the same data", |
| 53 | + "features": { |
| 54 | + "todo": {"description": "works with tasks"}, |
| 55 | + "todo": {"description": "works with events"}, |
| 56 | + } |
| 57 | + }, |
| 58 | + "search_includes_implicit_recurrences": { |
| 59 | + "description": "RFC says that the server MUST expand recurring components to determine whether any recurrence instances overlap the specified time range. Considered supported i.e. if a search for 2005 yields a yearly event happening first time in 2004.", |
| 60 | + "links": ["https://datatracker.ietf.org/doc/html/rfc4791#section-7.4"], |
| 61 | + "features": { |
| 62 | + "infinite-scope": { |
| 63 | + "description": "Needless to say, search on any future date range, no matter how far out in the future, should yield the recurring object" |
| 64 | + } |
| 65 | + } |
| 66 | + }, |
| 67 | + "expanded_search": { |
| 68 | + "description": "According to RFC 4791, the server MUST expand recurrence objects if asked for it - but many server doesn't do that. It doesn't matter much by now, as the client library can do the expandation. Some servers don't do expand at all, others deliver broken data, typically missing RECURRENCE-ID", |
| 69 | + "links": ["https://datatracker.ietf.org/doc/html/rfc4791#section-9.6.5"], |
| 70 | + "features": { |
| 71 | + "recurrence_exception_handling": { |
| 72 | + "description": "Server expand should work correctly also if a recurrence set with exceptions is given" |
| 73 | + }, |
| 74 | + }, |
| 75 | + }, |
| 76 | + }, |
| 77 | + }, |
| 78 | + } |
| 79 | + |
| 80 | + def __init__(self, feature_set): |
| 81 | + """ |
| 82 | + TODO: describe the feature_set better. |
| 83 | +
|
| 84 | + Should be a dict on the same style as self.FEATURES, but different. |
| 85 | +
|
| 86 | + Shortcuts accepted in the dict, like: |
| 87 | +
|
| 88 | + { |
| 89 | + "recurrences.search_includes_implicit_recurrences.infinite_scope": |
| 90 | + "unsupported" } |
| 91 | +
|
| 92 | + is equivalent with |
| 93 | +
|
| 94 | + { |
| 95 | + "recurrences": { |
| 96 | + "features": { |
| 97 | + "search_includes_inplicit_recurrences": { |
| 98 | + "infinite_scope": |
| 99 | + "support": "unsupported" }}}} |
| 100 | +
|
| 101 | + (TODO: is this sane? Am I reinventing a configuration language?) |
| 102 | + """ |
| 103 | + ## TODO: copy the FEATURES dict, or just the feature_set dict? |
| 104 | + ## (anyways, that is an internal design decision that may be |
| 105 | + ## changed ... but we need test code in place) |
| 106 | + self._server_features = {} |
| 107 | + self.copyFeatureSet(feature_set) |
| 108 | + |
| 109 | + def _copyFeature(def_node, server_node, value): |
| 110 | + if isinstance(value, str) and not 'support' in server_node: |
| 111 | + server_node['support'] = value |
| 112 | + elif isinstance(value, dict): |
| 113 | + if value.get('features'): |
| 114 | + raise NotImplementedError("todo ... work in progress ... need to recursively process the feature set") |
| 115 | + server_node.update(value) |
| 116 | + |
| 117 | + def copyFeatureSet(feature_set): |
| 118 | + for feature in feature_set: |
| 119 | + fpath = feature.split('.') |
| 120 | + def_tree = self.FEATURES |
| 121 | + server_tree = self._server_features |
| 122 | + for step in fpath: |
| 123 | + assert def_tree |
| 124 | + assert step in def_tree |
| 125 | + def_node = def_tree[step] |
| 126 | + if not step in server_tree: |
| 127 | + server_tree[step] = {} |
| 128 | + server_node = server_tree[step] |
| 129 | + if not 'features' in def_tree: |
| 130 | + server_tree = None |
| 131 | + def_tree = None |
| 132 | + else: |
| 133 | + if not 'features' in server_node: |
| 134 | + server_node['features'] = {} |
| 135 | + server_tree = server_node['features'] |
| 136 | + def_tree = def_node['features'] |
| 137 | + self._copyFeature(def_node, server_node, feature_set[fpath]) |
| 138 | + |
| 139 | + def find_feature(self, feature: str) -> dict: |
| 140 | + """ |
| 141 | + Feature should be a string like feature.subfeature.subsubfeature. |
| 142 | + |
| 143 | + Looks through the FEATURES list and returns the relevant section. |
| 144 | +
|
| 145 | + Will raise an AssertionError if feature is not found |
| 146 | + """ |
| 147 | + hierarchy = feature.split('.') |
| 148 | + node = self.FEATURES |
| 149 | + for x in hierarchy: |
| 150 | + assert x in node |
| 151 | + feature = node[x] |
| 152 | + node = feature.get('features', {}) |
| 153 | + return feature |
| 154 | + |
| 155 | +#### OLD STYLE |
14 | 156 |
|
15 | | -The issues may be grouped together, maybe even organized |
16 | | -hierarchically. I did consider organizing the compatibility issues in |
17 | | -some more advanced way, but I don't want to overcomplicate things - I |
18 | | -will try out the key-value-approach first. |
19 | | -""" |
20 | 157 | ## The lists below are specifying what tests should be skipped or |
21 | 158 | ## modified to accept non-conforming resultsets from the different |
22 | 159 | ## calendar servers. In addition there are some hacks in the library |
|
30 | 167 | ## * Perhaps some more readable format should be considered (yaml?). |
31 | 168 | ## * Consider how to get this into the documentation |
32 | 169 | incompatibility_description = { |
33 | | - 'rate_limited': |
34 | | - """It may be needed to pause a bit between each request when doing tests""", |
35 | | - |
36 | | - 'search_delay': |
37 | | - """Server populates indexes through some background job, so it takes some time from an event is added/edited until it's possible to search for it""", |
38 | | - |
39 | | - 'cleanup_calendar': |
40 | | - """Remove everything on the calendar for every test""", |
41 | | - |
42 | | - 'no_delete_calendar': |
43 | | - """Not allowed to delete calendars - or calendar ends up in a 'trashbin'""", |
44 | | - |
45 | | - 'broken_expand': |
46 | | - """Server-side expand seems to work, but delivers wrong data (typically missing RECURRENCE-ID)""", |
47 | | - |
48 | | - 'no_expand': |
49 | | - """Server-side expand does not seem to work""", |
50 | | - |
51 | | - 'broken_expand_on_exceptions': |
52 | | - """The testRecurringDateWithExceptionSearch test breaks as the icalendar_component is missing a RECURRENCE-ID field. TODO: should be investigated more""", |
53 | 170 |
|
54 | 171 | 'inaccurate_datesearch': |
55 | 172 | """A date search may yield results outside the search interval""", |
|
66 | 183 | 'no_current-user-principal': |
67 | 184 | """Current user principal not supported by the server (flag is ignored by the tests as for now - pass the principal URL as the testing URL and it will work, albeit with one warning""", |
68 | 185 |
|
69 | | - 'no_recurring': |
70 | | - """Server is having issues with recurring events and/or todos. """ |
71 | | - """date searches covering recurrances may yield no results, """ |
72 | | - """and events/todos may not be expanded with recurrances""", |
73 | | - |
74 | 186 | 'no_alarmsearch': |
75 | 187 | """Searching for alarms may yield too few or too many or even a 500 internal server error""", |
76 | 188 |
|
77 | | - 'no_recurring_todo': |
78 | | - """Recurring events are supported, but not recurring todos""", |
79 | 189 |
|
80 | | - 'no_recurring_todo_expand': |
81 | | - """Recurring todos aren't expanded (this is ignored by the tests now, as we're doing client-side expansion)""", |
82 | 190 |
|
83 | 191 | 'no_scheduling': |
84 | 192 | """RFC6833 is not supported""", |
|
290 | 398 | """Zimbra has the concept of task lists ... a calendar must either be a calendar with only events, or it can be a task list, but those must never be mixed""" |
291 | 399 | } |
292 | 400 |
|
293 | | -xandikos = [ |
| 401 | +xandikos = { |
| 402 | + "recurrences.expanded_search": {'support': 'ungraceful'}, |
| 403 | + "recurrences.search_includes_implicit_recurrences": {'support': 'unsupported'}, |
| 404 | + "old_flags": [ |
294 | 405 | ## https://github.com/jelmer/xandikos/issues/8 |
295 | | - "no_recurring", |
296 | | - |
297 | 406 | 'date_todo_search_ignores_duration', |
298 | 407 | 'text_search_is_exact_match_only', |
299 | 408 | "search_needs_comptype", |
|
316 | 425 |
|
317 | 426 | ## No alarm search (500 internal server error) |
318 | 427 | "no_alarmsearch", |
319 | | -] |
| 428 | + ] |
| 429 | +} |
320 | 430 |
|
321 | 431 | ## TODO - there has been quite some development in radicale recently, so this list |
322 | 432 | ## should probably be gone through |
323 | | -radicale = [ |
| 433 | +radicale = { |
| 434 | + "recurrences.expanded_search": {'support': 'ungraceful'}, |
| 435 | + 'old_flags': [ |
324 | 436 | ## calendar listings and calendar creation works a bit |
325 | 437 | ## "weird" on radicale |
326 | | - "broken_expand", |
327 | 438 | "no_default_calendar", |
328 | 439 | "no_alarmsearch", ## This is fixed and will be released soon |
329 | 440 |
|
|
342 | 453 | ## extra features not specified in RFC5545 |
343 | 454 | "calendar_order", |
344 | 455 | "calendar_color" |
345 | | -] |
| 456 | + ] |
| 457 | +} |
346 | 458 |
|
347 | 459 | ## ZIMBRA IS THE MOST SILLY, AND THERE ARE REGRESSIONS FOR EVERY RELEASE! |
348 | 460 | ## AAARGH! |
|
0 commit comments