#!/usr/bin/python3 # Copyright (C) 2026 Danilo M. # # 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")