diff options
| author | Craig Jennings <c@cjennings.net> | 2026-08-03 09:39:08 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-08-03 09:39:08 -0500 |
| commit | eb0c2191b77e795cca7884e0690ea51c515527fd (patch) | |
| tree | b6686bca1c7627d24e5fb6fd0b2df525db56d435 /tests/test-calendar-sync--batch-failures.el | |
| parent | 0eb65b6dcc2db54663b39dc7201c3dcf151add74 (diff) | |
| download | dotemacs-eb0c2191b77e795cca7884e0690ea51c515527fd.tar.gz dotemacs-eb0c2191b77e795cca7884e0690ea51c515527fd.zip | |
fix(calendar-sync): run the sync from a timer instead of the agenda hook
The hourly timer armed itself from org-agenda-mode-hook, so a session where
I never opened the agenda never synced at all. After a reboot that is every
session until the first agenda call. I found it with all three calendar
files still frozen at the pre-reboot write, five hours stale.
The deferral had a real reason. Feed URLs behind :secret-host live in
authinfo.gpg, and starting at load would prompt for a passphrase on a cold
gpg-agent. The cost was worse than what it bought.
A user timer owns the schedule now, every ten minutes.
calendar-sync-auto-start defaults to nil so the editor no longer arms its
own. scripts/calendar-sync-run is the batch entry point, and it blocks
until every calendar leaves the syncing state. The pipeline is asynchronous
end to end, so a batch Emacs that returns early exits zero having written
nothing.
Installing it on a machine that resolves its feeds through :secret-host
surfaced two bugs the inline-url machine could never show:
- cj/auth-source-secret-value called auth-source-search behind nothing but
a declare-function, which quiets the compiler and loads nothing. An
interactive Emacs always has auth-source in by the time anyone calls, so
the omission stayed invisible until a batch -Q sync died on a void
function. The require is guarded on fboundp. An unconditional one reloads
auth-source over whatever is already there, which replaced a caller's
stubbed search mid-call and sent an existing test out to the real
authinfo.
- A synchronous failure in one calendar aborted the whole loop. The async
callbacks record their own failures, but they never run when the error
lands before a process starts. Resolving a :secret-host feed signals
outright on a cold agent. Failures are contained per calendar now, so one
bad feed no longer costs the other two.
A failed row prints the reason it recorded. Batch Emacs discards the
*Messages* buffer the interactive path logs to, so without it the journal
shows only "error", with no way to tell a cold agent from a revoked token.
Diffstat (limited to 'tests/test-calendar-sync--batch-failures.el')
| -rw-r--r-- | tests/test-calendar-sync--batch-failures.el | 47 |
1 files changed, 47 insertions, 0 deletions
diff --git a/tests/test-calendar-sync--batch-failures.el b/tests/test-calendar-sync--batch-failures.el new file mode 100644 index 00000000..3190be21 --- /dev/null +++ b/tests/test-calendar-sync--batch-failures.el @@ -0,0 +1,47 @@ +;;; test-calendar-sync--batch-failures.el --- Batch failure filter tests -*- lexical-binding: t; -*- + +;;; Commentary: +;; `calendar-sync--batch-failures' picks the rows that did not finish cleanly. +;; The batch runner's exit code is derived from it, and systemd reads that exit +;; code, so the rule is deliberately strict: only `ok' passes. A calendar left +;; `syncing' at the timeout, or one that never started, is a failure -- both +;; states mean the org file on disk is not the calendar's current contents, +;; which is exactly the silent staleness the timer exists to prevent. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(require 'calendar-sync) + +(ert-deftest test-calendar-sync-batch-failures-keeps-only-non-ok () + "Normal: an errored calendar is returned and a healthy one is not." + (should (equal (calendar-sync--batch-failures + '(("google" . ok) ("proton" . error))) + '(("proton" . error))))) + +(ert-deftest test-calendar-sync-batch-failures-all-ok-is-empty () + "Normal: a fully successful run reports no failures." + (should (equal (calendar-sync--batch-failures + '(("google" . ok) ("proton" . ok))) + '()))) + +(ert-deftest test-calendar-sync-batch-failures-empty-input-is-empty () + "Boundary: no rows in, no rows out." + (should (equal (calendar-sync--batch-failures '()) '()))) + +(ert-deftest test-calendar-sync-batch-failures-timeout-counts-as-failure () + "Error: a calendar still `syncing' when the wait expired is a failure. +Its org file was not rewritten, so reporting success would hide the staleness." + (should (equal (calendar-sync--batch-failures + '(("google" . ok) ("proton" . syncing))) + '(("proton" . syncing))))) + +(ert-deftest test-calendar-sync-batch-failures-never-counts-as-failure () + "Error: a calendar that never started is a failure, not a skip." + (should (equal (calendar-sync--batch-failures '(("google" . never))) + '(("google" . never))))) + +(provide 'test-calendar-sync--batch-failures) +;;; test-calendar-sync--batch-failures.el ends here |
