* 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>
1.7 KiB
1.7 KiB