mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-15 17:25:26 -04:00
1209f258d7
* fix(caldav): don't prune the whole window when no objects could be parsed The post-sync prune deletes local origin=="caldav" rows in the window whose UID the server didn't just return. With an empty seen_uids it falls back to `uid.isnot(None)` — a match-all delete. That's right when the calendar is genuinely empty, but when the server returns objects and every one fails to parse (malformed iCal / an icalendar error), seen_uids is empty only because nothing could be read, so the match-all branch silently deletes every local event in the 90-day-back/365-day-forward window. Track whether any object failed to parse and gate the prune with a small pure helper `_should_prune_window(seen_uids, parse_failed)`: prune when something was read, or when the calendar is genuinely empty (no objects, no parse errors), but never when objects came back unreadable. Adds tests/test_caldav_prune_parse_failure.py for the three cases. * fix(caldav): skip the prune on any parse failure, not just total Review follow-up (#3454): _should_prune_window returned True whenever seen_uids was non-empty, so a partial parse failure (say 48 of 50 objects parse) still pruned the 2 unreadable-but-still-upstream events, because their UIDs were absent from seen_uids. Any parse failure makes seen_uids an incomplete view of the server, so pruning against it is unsafe whether the failure is total or partial. Skip the prune on any parse failure (return not parse_failed); only prune on a clean read (a genuinely empty window is still safe to prune). Tradeoff: one permanently-unparseable event pauses deletion mirroring until it is fixed, which is the safe direction (false-keep beats false-delete). Replace the now-incorrect "partial failure still prunes" assertion with a partial-failure regression: one object parses, one fails, so the prune is skipped and the unparsed event's local copy is not deleted. --------- Co-authored-by: Kenny Van de Maele <kenny@kvandemaele.be>
38 lines
1.7 KiB
Python
38 lines
1.7 KiB
Python
"""CalDAV sync must not prune the window when it can't fully read the server.
|
|
|
|
The prune deletes local caldav rows whose UID the server didn't return. `seen_uids`
|
|
is built only from objects that parsed, so any parse failure (total or partial)
|
|
makes it an incomplete view of the server:
|
|
|
|
- total failure: `seen_uids` is empty and the prune falls back to `uid.isnot(None)`
|
|
(match-all), wiping every event in the window;
|
|
- partial failure: the events that failed to parse are absent from `seen_uids`, so
|
|
`~uid.in_(seen_uids)` deletes those still-upstream events.
|
|
|
|
`_should_prune_window` therefore only allows the prune on a clean read.
|
|
"""
|
|
from src.caldav_sync import _should_prune_window
|
|
|
|
|
|
def test_prune_runs_on_clean_read():
|
|
# Clean read with events -> the normal ~uid.in_(seen) prune is safe.
|
|
assert _should_prune_window({"uid-a", "uid-b"}, parse_failed=False) is True
|
|
|
|
|
|
def test_prune_runs_when_calendar_genuinely_empty():
|
|
# Clean read, no objects -> genuinely empty window -> safe to prune.
|
|
assert _should_prune_window(set(), parse_failed=False) is True
|
|
|
|
|
|
def test_prune_skipped_when_all_objects_failed_to_parse():
|
|
# Every object failed -> empty seen_uids is "couldn't read", not "empty
|
|
# calendar" -> must NOT prune (would delete the whole window).
|
|
assert _should_prune_window(set(), parse_failed=True) is False
|
|
|
|
|
|
def test_prune_skipped_on_partial_parse_failure():
|
|
# Some objects parsed and at least one failed: seen_uids is incomplete, so
|
|
# pruning would delete the unparsed-but-still-upstream events. Skipping the
|
|
# prune keeps the local copy of the unparsed event instead of deleting it.
|
|
assert _should_prune_window({"parsed-uid"}, parse_failed=True) is False
|