aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/island/calendar/test_calctl.py
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/island/calendar/test_calctl.py')
-rw-r--r--desktop/island/calendar/test_calctl.py137
1 files changed, 137 insertions, 0 deletions
diff --git a/desktop/island/calendar/test_calctl.py b/desktop/island/calendar/test_calctl.py
new file mode 100644
index 0000000..e6acd10
--- /dev/null
+++ b/desktop/island/calendar/test_calctl.py
@@ -0,0 +1,137 @@
+#!/usr/bin/python3
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+"""Run: /usr/bin/python3 desktop/island/calendar/test_calctl.py
+
+Drives calctl as a CLI against temporary vdirs and a temporary khal config,
+never the live calendars. Plain asserts, no framework.
+"""
+
+import json
+import os
+import shutil
+import subprocess
+import tempfile
+from pathlib import Path
+
+CALCTL = Path(__file__).resolve().parent / "calctl"
+
+tmp = Path(tempfile.mkdtemp(prefix="calctl-test-"))
+(tmp / "a").mkdir()
+(tmp / "b").mkdir()
+(tmp / "khal").write_text(f"""[calendars]
+
+[[home]]
+path = {tmp}/a
+type = calendar
+color = light green
+
+[[birthdays]]
+path = {tmp}/b
+color = '#ff8800'
+
+[locale]
+firstweekday = 0
+
+[default]
+default_calendar = home
+""")
+ENV = dict(os.environ,
+ CALCTL_KHAL_CONFIG=str(tmp / "khal"),
+ CALCTL_OVERRIDES=str(tmp / "overrides.toml"),
+ CALCTL_NOSYNC="1",
+ TZ="Europe/Rome")
+
+
+def run(*args, ok=True):
+ p = subprocess.run([str(CALCTL), *args], env=ENV, capture_output=True, text=True)
+ r = json.loads(p.stdout)
+ assert r["ok"] is ok, (args, p.stdout, p.stderr)
+ assert (p.returncode == 0) is ok, (args, p.returncode)
+ return r["data"] if ok else r["error"]
+
+
+def put(path, body):
+ """A hand-written .ics, CRLF like a real one."""
+ Path(path).write_text(body.strip().replace("\n", "\r\n") + "\r\n")
+
+
+# calendars: khal colour names mapped to Theme names, a hex passed through.
+c = run("calendars")
+assert c["default"] == "home"
+assert [(x["name"], x["color"]) for x in c["calendars"]] == [("home", "green"), ("birthdays", "#ff8800")]
+assert c["calendars"][0]["path"] == str(tmp / "a")
+
+# A yearly all-day event from long ago is expanded into the asked window.
+put(tmp / "b" / "bday.ics", """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//test//EN
+BEGIN:VEVENT
+UID:bday-1
+DTSTAMP:20200101T000000Z
+DTSTART;VALUE=DATE:19900417
+DTEND;VALUE=DATE:19900418
+RRULE:FREQ=YEARLY;WKST=TU
+SUMMARY:Birthday
+END:VEVENT
+END:VCALENDAR
+""")
+apr = [e for e in run("list", "2026-04-01", "2026-05-01") if e["uid"] == "bday-1"]
+assert len(apr) == 1, apr
+assert apr[0]["start"] == "2026-04-17" and apr[0]["end"] == "2026-04-17", apr
+assert apr[0]["allDay"] is True and apr[0]["repeat"] == "yearly" and apr[0]["color"] == "#ff8800"
+
+# A rich rule and two alarms read as custom; the weekly rule is expanded in
+# local time, so 09:00 stays 09:00 across the DST change on 25 October.
+put(tmp / "a" / "weird.ics", """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//test//EN
+BEGIN:VEVENT
+UID:weird-1
+DTSTAMP:20260101T000000Z
+DTSTART;TZID=Europe/Rome:20261005T090000
+DTEND;TZID=Europe/Rome:20261005T100000
+RRULE:FREQ=WEEKLY;BYDAY=MO,WE
+SUMMARY:Standup
+X-FOO:bar
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:a
+TRIGGER:-PT1H
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:b
+TRIGGER:-PT5M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""")
+w = run("get", "weird-1")
+assert w["repeat"] == "custom" and w["alarm"] == "custom" and w["title"] == "Standup", w
+assert w["start"] == "2026-10-05T09:00", w
+occ = [e["start"] for e in run("list", "2026-10-01", "2026-11-01") if e["uid"] == "weird-1"]
+assert len(occ) == 8 and occ[0] == "2026-10-05T09:00" and "2026-10-26T09:00" in occ, occ
+
+# A broken file is skipped, not fatal.
+(tmp / "a" / "broken.ics").write_text("this is not a calendar\n")
+assert run("list", "2026-10-01", "2026-11-01")
+
+# Unknown uid and bad usage: the error envelope, exit 1.
+assert "no event" in run("get", "nope", ok=False)
+assert "usage" in run("frobnicate", ok=False)
+
+# --- writes (Task 3) go here ---
+
+shutil.rmtree(tmp)
+print("test_calctl: ok")