aboutsummaryrefslogtreecommitdiffstats
path: root/test_cal_notif.py
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-27 20:00:15 +0200
committerDanilo M. <danix@danix.xyz>2026-09-27 20:00:15 +0200
commit223292883da497b46caa0e01be0f4a1a3f134ea8 (patch)
tree2aba248109001d87be75447882e66b4e87c55023 /test_cal_notif.py
parent16f92566380d46132e7026e22c3c79aed2ddfbbb (diff)
downloadcal-notif-223292883da497b46caa0e01be0f4a1a3f134ea8.tar.gz
cal-notif-223292883da497b46caa0e01be0f4a1a3f134ea8.zip
Fix review findings: ACTION:NONE alarms, negative offsets, non-ASCII UIDs
- valarms()/lead() now skip RFC 9074 ACTION:NONE placeholder VALARMs, so those events fall through to the calendar default instead of going silent. - fmt_duration() renders negative timedeltas as "-Xd Yh Zm" instead of a wrapped-around wall-clock artifact. - save_overrides() writes JSON with ensure_ascii=False and utf-8 encoding, so non-BMP UIDs (e.g. emoji) round-trip through tomllib instead of being written as surrogate-pair escapes tomllib rejects. Marked as known limits with ponytail comments (no behaviour change): - valarms()/alarms_for(): offsets add in wall-clock time, off by the jump across a DST transition. - lead(): absolute DATE-TIME triggers don't widen the expansion window. - expand(): RDATE is ignored. - rule_for(): a VEVENT with more than one RRULE line fails and is skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'test_cal_notif.py')
-rw-r--r--test_cal_notif.py22
1 files changed, 21 insertions, 1 deletions
diff --git a/test_cal_notif.py b/test_cal_notif.py
index ea5aba5..f22fbc3 100644
--- a/test_cal_notif.py
+++ b/test_cal_notif.py
@@ -46,6 +46,7 @@ def test_durations():
pass
assert cn.fmt_duration(D + 2 * H) == "1d 2h"
assert cn.fmt_duration(timedelta()) == "0m"
+ assert cn.fmt_duration(timedelta(minutes=-45)) == "-45m"
assert cn.spoken_it(10 * M) == "dieci minuti"
assert cn.spoken_it(H) == "un'ora"
assert cn.spoken_it(D + 2 * H) == "un giorno e due ore"
@@ -56,7 +57,8 @@ def test_durations():
def test_overrides_roundtrip():
with tempfile.TemporaryDirectory() as d:
p = Path(d) / "overrides.toml"
- ov = {"a@example.org": ["1h", "10m"], 'q"uote@example.org': []}
+ ov = {"a@example.org": ["1h", "10m"], 'q"uote@example.org': [],
+ "café\U0001F600@example.org": ["30m"]}
cn.save_overrides(ov, p)
assert cn.load_overrides(p) == ov
assert cn.load_overrides(Path(d) / "missing.toml") == {}
@@ -135,6 +137,24 @@ END:VALARM"""
assert o.valarms == (at(2026, 1, 10, 10, 45),)
+def test_alarm_action_none():
+ lo, hi = at(2026, 1, 1), at(2026, 2, 1)
+ # RFC 9074 ACTION:NONE placeholder (Apple/iCloud "no alarm") must not
+ # count as a VALARM, so alarms_for falls back to the calendar default.
+ none_alarm = """UID:n@example.org
+DTSTART;TZID=Europe/Rome:20260115T090000
+SUMMARY:silent
+BEGIN:VALARM
+ACTION:NONE
+TRIGGER;VALUE=DATE-TIME:19760401T005545Z
+END:VALARM"""
+ (o,) = cn.expand(ics(none_alarm), "c", lo, hi)
+ assert o.valarms == ()
+ cfg = cn.load_config(Path("/nonexistent"))
+ cfg["defaults"] = {"c": ["15m"]}
+ assert cn.alarms_for(o, cfg, {}) == [o.start - 15 * M]
+
+
def test_load_events():
with tempfile.TemporaryDirectory() as d:
cal = Path(d) / "123"