From e78595096c1cb956602796c6b4b692e58458ff99 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 21 May 2026 15:38:56 -0400 Subject: feat(calendar-sync): resolve .ics feed URLs from auth-source A calendar's .ics feed URL is a secret token, so I'd rather not keep it in a plaintext config file. A calendar can now name a :secret-host, and calendar-sync--calendar-url looks the URL up in auth-source (~/.authinfo.gpg) at sync time. Inline :url still works and wins when both are set, so existing configs are unaffected. I added 7 tests covering the explicit-url, string-secret, function-secret, precedence, and no-match paths, and switched the .example template to the :secret-host shape. --- tests/test-calendar-sync--calendar-url.el | 86 +++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/test-calendar-sync--calendar-url.el (limited to 'tests') diff --git a/tests/test-calendar-sync--calendar-url.el b/tests/test-calendar-sync--calendar-url.el new file mode 100644 index 00000000..f18f5b55 --- /dev/null +++ b/tests/test-calendar-sync--calendar-url.el @@ -0,0 +1,86 @@ +;;; test-calendar-sync--calendar-url.el --- Tests for feed URL resolution -*- lexical-binding: t; -*- + +;;; Commentary: +;; Unit tests for `calendar-sync--calendar-url', which resolves the .ics +;; feed URL for a calendar plist. An explicit :url wins; otherwise the +;; URL is looked up in auth-source (e.g. ~/.authinfo.gpg) under the +;; calendar's :secret-host. auth-source-search is mocked at the boundary +;; so tests never touch disk or GPG. Covers Normal, Boundary, and Error. + +;;; Code: + +(require 'ert) +(require 'calendar-sync) + +(defmacro test-cs-url--with-auth (result &rest body) + "Run BODY with `auth-source-search' stubbed to return RESULT. +Records each call's :host argument into `calls', a binding BODY can read." + (declare (indent 1)) + `(let ((calls '())) + (cl-letf (((symbol-function 'auth-source-search) + (lambda (&rest args) + (push (plist-get args :host) calls) + ,result))) + ,@body))) + +;;; Normal + +(ert-deftest test-calendar-sync--calendar-url-normal-explicit-url () + "Normal: an explicit :url is returned verbatim." + (test-cs-url--with-auth nil + (should (equal "https://example.com/feed.ics" + (calendar-sync--calendar-url + '(:name "x" :url "https://example.com/feed.ics")))) + ;; auth-source must not be consulted when :url is present + (should (null calls)))) + +(ert-deftest test-calendar-sync--calendar-url-normal-string-secret () + "Normal: :secret-host resolves via auth-source with a string secret." + (test-cs-url--with-auth (list (list :host "calendar-google" + :secret "https://g.example/basic.ics")) + (should (equal "https://g.example/basic.ics" + (calendar-sync--calendar-url + '(:name "google" :secret-host "calendar-google")))) + (should (equal '("calendar-google") calls)))) + +(ert-deftest test-calendar-sync--calendar-url-normal-function-secret () + "Normal: a function-valued :secret is funcalled (the netrc backend's form)." + (test-cs-url--with-auth (list (list :host "calendar-proton" + :secret (lambda () "https://p.example/cal.ics"))) + (should (equal "https://p.example/cal.ics" + (calendar-sync--calendar-url + '(:name "proton" :secret-host "calendar-proton")))))) + +;;; Boundary + +(ert-deftest test-calendar-sync--calendar-url-boundary-explicit-url-wins () + "Boundary: when both :url and :secret-host are set, :url wins and +auth-source is never consulted." + (test-cs-url--with-auth (list (list :host "h" :secret "from-authinfo")) + (should (equal "from-url" + (calendar-sync--calendar-url + '(:name "x" :url "from-url" :secret-host "h")))) + (should (null calls)))) + +;;; Error + +(ert-deftest test-calendar-sync--calendar-url-error-neither-key () + "Error: a calendar with neither :url nor :secret-host yields nil." + (test-cs-url--with-auth (list (list :host "h" :secret "should-not-reach")) + (should (null (calendar-sync--calendar-url '(:name "x" :file "f")))) + (should (null calls)))) + +(ert-deftest test-calendar-sync--calendar-url-error-no-match () + "Error: :secret-host with no auth-source match yields nil." + (test-cs-url--with-auth nil + (should (null (calendar-sync--calendar-url + '(:name "x" :secret-host "calendar-missing")))))) + +(ert-deftest test-calendar-sync--calendar-url-error-match-without-secret () + "Error: a match lacking a :secret yields nil, not an error." + (test-cs-url--with-auth (list (list :host "calendar-google")) + (should (null (calendar-sync--calendar-url + '(:name "x" :secret-host "calendar-google")))))) + +(provide 'test-calendar-sync--calendar-url) +;;; test-calendar-sync--calendar-url.el ends here -- cgit v1.2.3