aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--modules/calendar-sync.el40
-rw-r--r--tests/test-calendar-sync-no-config-startup.el130
3 files changed, 157 insertions, 14 deletions
diff --git a/.gitignore b/.gitignore
index af429c22..28b3f21e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,7 @@ auto-save-list/
/.org-generic-id-locations
/multisession/
/browser-choice.el
+/calendar-sync.local.el
/client_secret_491339091045-sjje1r54s22vn2ugh45khndjafp89vto.apps.googleusercontent.com.json
# reveal.js local clone (managed by scripts/setup-reveal.sh)
diff --git a/modules/calendar-sync.el b/modules/calendar-sync.el
index 06bee213..b232567a 100644
--- a/modules/calendar-sync.el
+++ b/modules/calendar-sync.el
@@ -69,10 +69,12 @@
;;; Code:
-(require 'user-constants) ; For gcal-file, pcal-file paths
-
;;; Configuration
+(defgroup calendar-sync nil
+ "One-way calendar synchronization to Org files."
+ :group 'calendar)
+
(defvar calendar-sync-calendars nil
"List of calendars to sync.
Each calendar is a plist with the following keys:
@@ -89,17 +91,13 @@ Example:
:url \"https://calendar.proton.me/api/calendar/v1/url/.../calendar.ics\"
:file pcal-file)))")
-;; Calendar sync (one-way: Google/Proton → Org)
-(setq calendar-sync-calendars
- `((:name "google"
- :url "https://calendar.google.com/calendar/ical/craigmartinjennings%40gmail.com/private-1dad154d6a2100e755f76e2d0502f6aa/basic.ics"
- :file ,gcal-file)
- (:name "proton"
- :url "https://calendar.proton.me/api/calendar/v1/url/MpLtuwsUNoygyA_60GvJE5cz0hbREbrAPBEJoWDRpFEstnmzmEMDb7sjLzkY8kbkF10A7Be3wGKB1-vqaLf-pw==/calendar.ics?CacheKey=LrB9NG5Vfqp5p2sy90H13g%3D%3D&PassphraseKey=sURqFfACPM21d6AXSeaEXYCruimvSb8t0ce1vuxRAXk%3D"
- :file ,pcal-file)
- (:name "deepsat"
- :url "https://calendar.google.com/calendar/ical/craig.jennings%40deepsat.com/private-f0250a2c6752a5ca71d7b0636587a6d5/basic.ics"
- :file ,dcal-file)))
+(defcustom calendar-sync-private-config-file
+ (expand-file-name "calendar-sync.local.el" user-emacs-directory)
+ "Private calendar-sync config file loaded when readable.
+This file is the intended place to set `calendar-sync-calendars' with private
+calendar feed URLs."
+ :type 'file
+ :group 'calendar-sync)
(defvar calendar-sync-interval-minutes 60
"Sync interval in minutes.
@@ -1229,6 +1227,16 @@ Creates parent directories if needed."
;;; Debug Logging
+(defun calendar-sync--load-private-config ()
+ "Load private calendar-sync configuration when available."
+ (when (file-readable-p calendar-sync-private-config-file)
+ (condition-case err
+ (load calendar-sync-private-config-file nil t)
+ (error
+ (message "calendar-sync: Failed to load private config %s: %s"
+ (abbreviate-file-name calendar-sync-private-config-file)
+ (error-message-string err))))))
+
(defun calendar-sync--debug-p ()
"Return non-nil if calendar-sync debug logging is enabled.
Checks `cj/debug-modules' for symbol `calendar-sync' or t (all)."
@@ -1446,6 +1454,8 @@ Syncs all calendars immediately, then every `calendar-sync-interval-minutes'."
;;; Initialization
+(calendar-sync--load-private-config)
+
;; Load saved state from previous session
(calendar-sync--load-state)
@@ -1465,7 +1475,9 @@ Syncs all calendars immediately, then every `calendar-sync-interval-minutes'."
;; Start auto-sync if enabled and calendars are configured
;; Syncs immediately then every calendar-sync-interval-minutes (default: 60 minutes)
-(when (and calendar-sync-auto-start calendar-sync-calendars)
+(when (and calendar-sync-auto-start
+ calendar-sync-calendars
+ (not noninteractive))
(calendar-sync-start))
(provide 'calendar-sync)
diff --git a/tests/test-calendar-sync-no-config-startup.el b/tests/test-calendar-sync-no-config-startup.el
new file mode 100644
index 00000000..94a53009
--- /dev/null
+++ b/tests/test-calendar-sync-no-config-startup.el
@@ -0,0 +1,130 @@
+;;; test-calendar-sync-no-config-startup.el --- No-config startup tests for calendar-sync -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Regression tests for loading calendar-sync without configured calendars.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+(defvar calendar-sync-private-config-file)
+(defvar calendar-sync-calendars)
+(defvar calendar-sync-auto-start)
+
+(ert-deftest test-calendar-sync-no-config-startup-does-not-start-sync ()
+ "Loading calendar-sync without calendars should not start timers or fetches."
+ (let ((calendar-sync-private-config-file
+ (expand-file-name "missing-calendar-sync.local.el" temporary-file-directory))
+ (calendar-sync-calendars nil)
+ (calendar-sync-auto-start t)
+ (timer-called nil)
+ (process-called nil)
+ messages)
+ (cl-letf (((symbol-function 'run-at-time)
+ (lambda (&rest _args)
+ (setq timer-called t)
+ 'test-calendar-sync-timer))
+ ((symbol-function 'make-process)
+ (lambda (&rest _args)
+ (setq process-called t)
+ nil))
+ ((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (load (expand-file-name "modules/calendar-sync.el" user-emacs-directory)
+ nil t))
+ (should (boundp 'calendar-sync-calendars))
+ (should-not calendar-sync-calendars)
+ (should-not timer-called)
+ (should-not process-called)
+ (should-not (cl-some (lambda (msg)
+ (string-match-p "calendar-sync: Syncing" msg))
+ messages))))
+
+(ert-deftest test-calendar-sync-no-config-status-reports-missing-config ()
+ "Status should report missing calendar config without erroring."
+ (let ((calendar-sync-private-config-file
+ (expand-file-name "missing-calendar-sync.local.el" temporary-file-directory))
+ (calendar-sync-calendars nil)
+ (calendar-sync-auto-start t)
+ messages)
+ (cl-letf (((symbol-function 'run-at-time) (lambda (&rest _args) nil))
+ ((symbol-function 'make-process) (lambda (&rest _args) nil))
+ ((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (load (expand-file-name "modules/calendar-sync.el" user-emacs-directory)
+ nil t)
+ (calendar-sync-status))
+ (should (member "calendar-sync: No calendars configured (set calendar-sync-calendars)"
+ messages))))
+
+(ert-deftest test-calendar-sync-no-config-loads-private-config-when-present ()
+ "Loading calendar-sync should read an ignored private config file when present."
+ (let ((config-file (make-temp-file "calendar-sync-local-" nil ".el"))
+ (calendar-sync-calendars nil)
+ (calendar-sync-auto-start t)
+ (timer-called nil)
+ (process-called nil))
+ (unwind-protect
+ (progn
+ (with-temp-file config-file
+ (insert "(setq calendar-sync-auto-start nil)\n")
+ (insert "(setq calendar-sync-calendars\n")
+ (insert " '((:name \"local\" :url \"https://example.test/calendar.ics\" :file \"/tmp/local.org\")))\n"))
+ (let ((calendar-sync-private-config-file config-file))
+ (cl-letf (((symbol-function 'run-at-time)
+ (lambda (&rest _args)
+ (setq timer-called t)
+ 'test-calendar-sync-timer))
+ ((symbol-function 'make-process)
+ (lambda (&rest _args)
+ (setq process-called t)
+ nil))
+ ((symbol-function 'message)
+ (lambda (&rest _args) nil)))
+ (load (expand-file-name "modules/calendar-sync.el" user-emacs-directory)
+ nil t))
+ (should (equal (calendar-sync--calendar-names) '("local")))
+ (should-not calendar-sync-auto-start)
+ (should-not timer-called)
+ (should-not process-called)))
+ (delete-file config-file))))
+
+(ert-deftest test-calendar-sync-no-config-private-config-does-not-auto-start-in-batch ()
+ "Private config should not auto-start sync while Emacs is noninteractive."
+ (let ((config-file (make-temp-file "calendar-sync-local-" nil ".el"))
+ (calendar-sync-calendars nil)
+ (calendar-sync-auto-start t)
+ (timer-called nil)
+ (process-called nil))
+ (unwind-protect
+ (progn
+ (with-temp-file config-file
+ (insert "(setq calendar-sync-calendars\n")
+ (insert " '((:name \"local\" :url \"https://example.test/calendar.ics\" :file \"/tmp/local.org\")))\n"))
+ (let ((calendar-sync-private-config-file config-file))
+ (cl-letf (((symbol-function 'run-at-time)
+ (lambda (&rest _args)
+ (setq timer-called t)
+ 'test-calendar-sync-timer))
+ ((symbol-function 'make-process)
+ (lambda (&rest _args)
+ (setq process-called t)
+ nil))
+ ((symbol-function 'message)
+ (lambda (&rest _args) nil)))
+ (load (expand-file-name "modules/calendar-sync.el" user-emacs-directory)
+ nil t))
+ (should (equal (calendar-sync--calendar-names) '("local")))
+ (should calendar-sync-auto-start)
+ (should noninteractive)
+ (should-not timer-called)
+ (should-not process-called)))
+ (delete-file config-file))))
+
+(provide 'test-calendar-sync-no-config-startup)
+;;; test-calendar-sync-no-config-startup.el ends here