Skip to content

Commit 2f2bf2c

Browse files
tobixenclaude
andcommitted
test: gate the comp-type-less time-range Run 2 on a ReportError rejection
CI failure on Cyrus: testSearchWithoutCompTypeWithDateRange's second run forced search.time-range.comp-type-optional ON and asserted the event was returned, relying on the reactive HTTP-400 fallback. But that fallback only recovers from a ReportError (SabreDAV's 400) - Cyrus instead returns nothing (CI) or 403 (locally), so the forced run either found nothing or crashed. The forced "Run 2" is only meaningful where the raw comp-type-less time-range query raises a ReportError (Baikal, Nextcloud). The test now probes the raw behaviour first and only runs Run 2 in that case; other servers exercise just the proactive split (Run 1). The reactive fallback itself remains covered deterministically by the unit test. #681 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9b5f526 commit 2f2bf2c

4 files changed

Lines changed: 70 additions & 31 deletions

File tree

docs/source/http-libraries.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ According to
3030
https://github.com/python-caldav/caldav/issues/611#issuecomment-4278875543
3131
the httpx development seems stagnant, and httpx is even flagged as a
3232
supply-chain risk in some Reddit-discussions. It seems like the http
33-
user space is filled with drama and intrigues.
33+
user space is filled with drama and intrigues. httpxyz is a
34+
maintained fork of httpx. For async communication, the fallback chain
35+
now is niquests, httpxyz and finally httpx if import of the former two
36+
fails.
3437

3538
Fallbacks
3639
---------
3740

38-
To enable the fallbacks, just ensure the requests and/or httpx library is available and that niquests isn't available. In virtual environments, fix the dependencies in `pyproject.toml`.
41+
To enable the fallbacks, just ensure the requests and/or httpxyz/httpx library is available and that niquests isn't available. In virtual environments, fix the dependencies in `pyproject.toml`.
3942

4043
Recommendations
4144
---------------

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ classifiers = [
5353

5454
dependencies = [
5555
"lxml",
56-
"niquests",
56+
"niquests", ## see docs/source/http-libraries.rst
5757
"recurring-ical-events>=2.0.0",
5858
"typing_extensions;python_version<'3.11'",
5959
"icalendar>6.0.0",

tests/test_async_integration.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -577,21 +577,39 @@ async def _assert_event_found():
577577
## Run 1: the server's real feature configuration (proactive comp-type split)
578578
await _assert_event_found()
579579

580-
## Run 2: force search.time-range.comp-type-optional ON, so the library
581-
## sends the comp-type-less time-range query verbatim and must recover from
582-
## the server's rejection via the reactive fallback (issue #681 item 4).
583-
features = async_calendar.client.features
584-
key = "search.time-range.comp-type-optional"
585-
had_key = key in features._server_features
586-
saved = features._server_features.get(key)
587-
features.set_feature(key, {"support": "full"})
580+
## Determine how this server reacts to the raw comp-type-less time-range
581+
## query. Only SabreDAV-style servers reject it with a ReportError (HTTP
582+
## 400) - the case the reactive fallback (issue #681 item 4) recovers from.
583+
## Others return nothing or a different error (e.g. Cyrus may answer 403),
584+
## where forcing the feature on is an unrecoverable misconfiguration.
585+
from caldav.lib import error
586+
588587
try:
589-
await _assert_event_found()
590-
finally:
591-
if had_key:
592-
features._server_features[key] = saved
593-
else:
594-
features._server_features.pop(key, None)
588+
await async_calendar.search(start=start, end=end, compatibility_workarounds=False)
589+
raw_report_error = False
590+
except error.ReportError:
591+
raw_report_error = True
592+
except error.DAVError:
593+
raw_report_error = False
594+
595+
## Run 2 (only meaningful where the raw query raises a ReportError): force
596+
## the feature ON and verify the reactive fallback recovers and finds the event.
597+
if raw_report_error:
598+
features = async_calendar.client.features
599+
key = "search.time-range.comp-type-optional"
600+
had_key = key in features._server_features
601+
saved = features._server_features.get(key)
602+
features.set_feature(key, {"support": "full"})
603+
try:
604+
objects = await async_calendar.search(start=start, end=end)
605+
assert [o for o in objects if uid in o.data], (
606+
"reactive fallback did not recover the comp-type-less time-range search"
607+
)
608+
finally:
609+
if had_key:
610+
features._server_features[key] = saved
611+
else:
612+
features._server_features.pop(key, None)
595613

596614
@pytest.mark.asyncio
597615
async def test_search_without_comptype_with_category(self, async_calendar: Any) -> None:

tests/test_caldav.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3466,21 +3466,39 @@ def _assert_event_found():
34663466
## Run 1: the server's real feature configuration (proactive comp-type split)
34673467
_assert_event_found()
34683468

3469-
## Run 2: force search.time-range.comp-type-optional ON, so the library
3470-
## sends the comp-type-less time-range query verbatim and must recover from
3471-
## the server's rejection via the reactive fallback (issue #681 item 4).
3472-
features = self.caldav.features
3473-
key = "search.time-range.comp-type-optional"
3474-
had_key = key in features._server_features
3475-
saved = features._server_features.get(key)
3476-
features.set_feature(key, {"support": "full"})
3469+
## Determine how this server reacts to the raw comp-type-less time-range
3470+
## query. Only SabreDAV-style servers (Baikal, Nextcloud) reject it with a
3471+
## ReportError (HTTP 400) - that is the case the reactive fallback (issue
3472+
## #681 item 4) is designed to recover from. Others return nothing, or a
3473+
## different error (e.g. Cyrus may answer 403), where forcing the feature on
3474+
## is an unrecoverable misconfiguration not worth asserting on.
34773475
try:
3478-
_assert_event_found()
3479-
finally:
3480-
if had_key:
3481-
features._server_features[key] = saved
3482-
else:
3483-
features._server_features.pop(key, None)
3476+
cal.search(start=start, end=end, compatibility_workarounds=False)
3477+
raw_report_error = False
3478+
except error.ReportError:
3479+
raw_report_error = True
3480+
except error.DAVError:
3481+
raw_report_error = False
3482+
3483+
## Run 2 (only meaningful where the raw query raises a ReportError): force
3484+
## search.time-range.comp-type-optional ON and verify the reactive fallback
3485+
## recovers and still finds the event.
3486+
if raw_report_error:
3487+
features = self.caldav.features
3488+
key = "search.time-range.comp-type-optional"
3489+
had_key = key in features._server_features
3490+
saved = features._server_features.get(key)
3491+
features.set_feature(key, {"support": "full"})
3492+
try:
3493+
objects = cal.search(start=start, end=end)
3494+
assert [o for o in objects if uid in o.data], (
3495+
"reactive fallback did not recover the comp-type-less time-range search"
3496+
)
3497+
finally:
3498+
if had_key:
3499+
features._server_features[key] = saved
3500+
else:
3501+
features._server_features.pop(key, None)
34843502

34853503
def testSearchWithoutCompTypeWithCategory(self):
34863504
"""Test for https://github.com/python-caldav/caldav/issues/681

0 commit comments

Comments
 (0)