Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Purelymail support (WIP) #442

Merged
merged 2 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions caldav/davclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ def _parse_response(self, response) -> Tuple[str, List[_Element], Optional[Any]]
else:
error.assert_(False)
error.assert_(href)
## TODO: is this safe/sane?
## Ref https://github.com/python-caldav/caldav/issues/435 the paths returned may be absolute URLs,
## but the caller expects them to be paths. Could we have issues when a server has same path
## but different URLs for different elements?
if ":" in href:
href = unquote(URL(href).path)
return (cast(str, href), propstats, status)

def find_objects_and_props(self) -> Dict[str, Dict[str, _Element]]:
Expand Down
27 changes: 26 additions & 1 deletion tests/compatibility_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,11 @@
"""The server may not always come up with anything useful when searching for objects and omitting to specify weather one wants to see tasks or events""",

'robur_rrule_freq_yearly_expands_monthly':
"""Robur expands a yearly event into a monthly event. I believe I've reported this one upstream at some point, but can't find back to it"""
"""Robur expands a yearly event into a monthly event. I believe I've reported this one upstream at some point, but can't find back to it""",

'no_search':
"""Apparently the calendar server does not support search at all (this often implies that 'object_by_uid_is_broken' has to be set as well)"""

}

xandikos = [
Expand Down Expand Up @@ -387,5 +391,26 @@
'combined_search_not_working'
]

calendar_mail_ru = [
'no_mkcalendar', ## weird. It was working in early June 2024, then it stopped working in mid-June 2024.
'no_current-user-principal',
'no_todo',
'no_journal',
'search_always_needs_comptype',
'no_sync_token', ## don't know if sync tokens are supported or not - the sync-token-code needs some workarounds ref https://github.com/python-caldav/caldav/issues/401
'text_search_not_working',
'isnotdefined_not_working',
'no_scheduling_mailbox',
'no_freebusy_rfc4791',
'no_relships', ## mail.ru recreates the icalendar content, and strips everything it doesn't know anyhting about, including relationship info
]

purelymail = [
'no_scheduling',
'non_existing_calendar_found',
'no_supported_components_support',
'no_search',
'object_by_uid_is_broken',
]

# fmt: on
66 changes: 39 additions & 27 deletions tests/test_caldav.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,11 +521,6 @@ def setup_method(self):

self._cleanup("pre")

if self.check_compatibility_flag("object_by_uid_is_broken"):
import caldav.objects

caldav.objects.NotImplementedError = SkipTest

logging.debug("##############################")
logging.debug("############## test setup done")
logging.debug("##############################")
Expand Down Expand Up @@ -1199,6 +1194,7 @@ def testGetSupportedComponents(self):

def testSearchEvent(self):
self.skip_on_compatibility_flag("read_only")
self.skip_on_compatibility_flag("no_search")
c = self._fixCalendar()
c.save_event(ev1)
c.save_event(ev3)
Expand Down Expand Up @@ -1341,6 +1337,7 @@ def testSearchEvent(self):
def testSearchSortTodo(self):
self.skip_on_compatibility_flag("read_only")
self.skip_on_compatibility_flag("no_todo")
self.skip_on_compatibility_flag("no_search")
c = self._fixCalendar(supported_calendar_component_set=["VTODO"])
t1 = c.save_todo(
summary="1 task overdue",
Expand Down Expand Up @@ -1401,6 +1398,7 @@ def check_order(tasks, order):
def testSearchTodos(self):
self.skip_on_compatibility_flag("read_only")
self.skip_on_compatibility_flag("no_todo")
self.skip_on_compatibility_flag("no_search")
c = self._fixCalendar(supported_calendar_component_set=["VTODO"])

t1 = c.save_todo(todo)
Expand Down Expand Up @@ -1510,6 +1508,7 @@ def testWrongPassword(self):
def testCreateChildParent(self):
self.skip_on_compatibility_flag("read_only")
self.skip_on_compatibility_flag("no_relships")
self.skip_on_compatibility_flag("object_by_uid_is_broken")
c = self._fixCalendar(supported_calendar_component_set=["VEVENT"])
parent = c.save_event(
dtstart=datetime(2022, 12, 26, 19, 15),
Expand Down Expand Up @@ -1621,6 +1620,7 @@ def testSetDue(self):
some_todo.save()

self.skip_on_compatibility_flag("no_relships")
self.skip_on_compatibility_flag("object_by_uid_is_broken")
parent = c.save_todo(
dtstart=datetime(2022, 12, 26, 19, 00, tzinfo=utc),
dtend=datetime(2022, 12, 26, 21, 00, tzinfo=utc),
Expand Down Expand Up @@ -1689,6 +1689,7 @@ def testCreateJournalListAndJournalEntry(self):
j1 = c.save_journal(journal)
journals = c.journals()
assert len(journals) == 1
self.skip_on_compatibility_flag("object_by_uid_is_broken")
j1_ = c.journal_by_uid(j1.id)
j1_.icalendar_instance
journals[0].icalendar_instance
Expand Down Expand Up @@ -1819,6 +1820,7 @@ def testTodoDatesearch(self):
# bedeworks does not support VTODO
self.skip_on_compatibility_flag("no_todo")
self.skip_on_compatibility_flag("no_todo_datesearch")
self.skip_on_compatibility_flag("no_search")
c = self._fixCalendar(supported_calendar_component_set=["VTODO"])

# add todo-item
Expand Down Expand Up @@ -1969,10 +1971,11 @@ def testTodoCompletion(self):
# The historic todo-item can still be accessed
todos = c.todos(include_completed=True)
assert len(todos) == 3
t3_ = c.todo_by_uid(t3.id)
assert t3_.instance.vtodo.summary == t3.instance.vtodo.summary
assert t3_.instance.vtodo.uid == t3.instance.vtodo.uid
assert t3_.instance.vtodo.dtstart == t3.instance.vtodo.dtstart
if not self.check_compatibility_flag("object_by_uid_is_broken"):
t3_ = c.todo_by_uid(t3.id)
assert t3_.instance.vtodo.summary == t3.instance.vtodo.summary
assert t3_.instance.vtodo.uid == t3.instance.vtodo.uid
assert t3_.instance.vtodo.dtstart == t3.instance.vtodo.dtstart

t2.delete()

Expand Down Expand Up @@ -2194,9 +2197,10 @@ def testLookupEvent(self):
e2 = c.event_by_url(e1.url)
assert e2.instance.vevent.uid == e1.instance.vevent.uid
assert e2.url == e1.url
e3 = c.event_by_uid("[email protected]")
assert e3.instance.vevent.uid == e1.instance.vevent.uid
assert e3.url == e1.url
if not self.check_compatibility_flag("object_by_uid_is_broken"):
e3 = c.event_by_uid("[email protected]")
assert e3.instance.vevent.uid == e1.instance.vevent.uid
assert e3.url == e1.url

# Knowing the URL of an event, we should be able to get to it
# without going through a calendar object
Expand All @@ -2220,9 +2224,10 @@ def testCreateOverwriteDeleteEvent(self):
c = self._fixCalendar()
assert c.url is not None

# attempts on updating/overwriting a non-existing event should fail
with pytest.raises(error.ConsistencyError):
c.save_event(ev1, no_create=True)
# attempts on updating/overwriting a non-existing event should fail (unless object_by_uid_is_broken):
if not self.check_compatibility_flag("object_by_uid_is_broken"):
with pytest.raises(error.ConsistencyError):
c.save_event(ev1, no_create=True)

# no_create and no_overwrite is mutually exclusive, this will always
# raise an error (unless the ical given is blank)
Expand All @@ -2242,7 +2247,11 @@ def testCreateOverwriteDeleteEvent(self):
assert t1.url is not None
if not self.check_compatibility_flag("event_by_url_is_broken"):
assert c.event_by_url(e1.url).url == e1.url
assert c.event_by_uid(e1.id).url == e1.url
if not self.check_compatibility_flag("object_by_uid_is_broken"):
assert c.event_by_uid(e1.id).url == e1.url

## no_create will not work unless object_by_uid works
no_create = not self.check_compatibility_flag("object_by_uid_is_broken")

## add same event again. As it has same uid, it should be overwritten
## (but some calendars may throw a "409 Conflict")
Expand All @@ -2254,34 +2263,35 @@ def testCreateOverwriteDeleteEvent(self):
t2 = c.save_todo(todo)

## add same event with "no_create". Should work like a charm.
e2 = c.save_event(ev1, no_create=True)
e2 = c.save_event(ev1, no_create=no_create)
if not self.check_compatibility_flag(
"no_todo"
) and not self.check_compatibility_flag("no_todo_on_standard_calendar"):
t2 = c.save_todo(todo, no_create=True)
t2 = c.save_todo(todo, no_create=no_create)

## this should also work.
e2.instance.vevent.summary.value = e2.instance.vevent.summary.value + "!"
e2.save(no_create=True)
e2.save(no_create=no_create)

if not self.check_compatibility_flag(
"no_todo"
) and not self.check_compatibility_flag("no_todo_on_standard_calendar"):
t2.instance.vtodo.summary.value = t2.instance.vtodo.summary.value + "!"
t2.save(no_create=True)
t2.save(no_create=no_create)

if not self.check_compatibility_flag("event_by_url_is_broken"):
e3 = c.event_by_url(e1.url)
assert e3.instance.vevent.summary.value == "Bastille Day Party!"

## "no_overwrite" should throw a ConsistencyError
with pytest.raises(error.ConsistencyError):
c.save_event(ev1, no_overwrite=True)
if not self.check_compatibility_flag(
"no_todo"
) and not self.check_compatibility_flag("no_todo_on_standard_calendar"):
## "no_overwrite" should throw a ConsistencyError. But it depends on object_by_uid.
if not self.check_compatibility_flag("object_by_uid_is_broken"):
with pytest.raises(error.ConsistencyError):
c.save_todo(todo, no_overwrite=True)
c.save_event(ev1, no_overwrite=True)
if not self.check_compatibility_flag(
"no_todo"
) and not self.check_compatibility_flag("no_todo_on_standard_calendar"):
with pytest.raises(error.ConsistencyError):
c.save_todo(todo, no_overwrite=True)

# delete event
e1.delete()
Expand Down Expand Up @@ -2312,6 +2322,7 @@ def testDateSearchAndFreeBusy(self):
non-recurring event
"""
self.skip_on_compatibility_flag("read_only")
self.skip_on_compatibility_flag("no_search")
# Create calendar, add event ...
c = self._fixCalendar()
assert c.url is not None
Expand Down Expand Up @@ -2395,6 +2406,7 @@ def testRecurringDateSearch(self):
"""
self.skip_on_compatibility_flag("read_only")
self.skip_on_compatibility_flag("no_recurring")
self.skip_on_compatibility_flag("no_search")
c = self._fixCalendar()

# evr is a yearly event starting at 1997-02-11
Expand Down
Loading