From b74b98f177d92d50ddbede900ba41212e07c5f63 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 21 Feb 2026 07:06:50 -0600 Subject: feat: unified cache and staleness handling for mode-line and buffer Replace TTL-based cache invalidation with proactive scheduled refresh. Both mode-line and buffer systems now follow: timer refreshes cache, display reads from cache, staleness indicated when data is old. Phase 1 - Mode-line cache formalization + staleness display: - Replace wttrin--mode-line-tooltip-data with wttrin--mode-line-cache as (timestamp . data) cons cell matching buffer cache pattern - Add wttrin--format-age helper for human-readable age strings - Rewrite wttrin--mode-line-update-display to take no arguments, read from cache, compute staleness (age > 2x refresh interval), dim emoji gray when stale, show staleness info in tooltip - Rewrite wttrin--mode-line-fetch-weather to write cache on success, show stale display on failure with cache, error placeholder without - Add wttrin--mode-line-update-placeholder-error for first-launch failure Phase 2 - Remove TTL, add proactive buffer refresh: - Rename wttrin-cache-ttl to wttrin-refresh-interval (default 3600s) with define-obsolete-variable-alias for backward compatibility - Change wttrin-mode-line-refresh-interval default from 900 to 3600 - Remove TTL check from wttrin--get-cached-or-fetch; serve cached data regardless of age, background timer keeps it fresh - Add buffer refresh timer (wttrin--buffer-cache-refresh) Phase 3 - Buffer staleness display: - Add wttrin--format-staleness-header for buffer age display - Insert staleness line in wttrin--display-weather before instructions Phase 4 - Cleanup: - Remove all references to wttrin--mode-line-tooltip-data - Update README.org cache settings and mode-line documentation - Update tests for new API (198 tests across 21 files, all passing) --- tests/test-wttrin--get-cached-or-fetch.el | 109 ++++++------------------------ 1 file changed, 19 insertions(+), 90 deletions(-) (limited to 'tests/test-wttrin--get-cached-or-fetch.el') diff --git a/tests/test-wttrin--get-cached-or-fetch.el b/tests/test-wttrin--get-cached-or-fetch.el index 77a2689..e3383c6 100644 --- a/tests/test-wttrin--get-cached-or-fetch.el +++ b/tests/test-wttrin--get-cached-or-fetch.el @@ -5,7 +5,9 @@ ;;; Commentary: ;; Unit tests for wttrin--get-cached-or-fetch function. -;; Tests the core cache workflow: cache hits, misses, expiration, and error fallback. +;; Tests the core cache workflow: cache hits, misses, and error fallback. +;; TTL-based expiration has been removed — cached data is served regardless +;; of age, with proactive refresh keeping data fresh in the background. ;;; Code: @@ -36,7 +38,7 @@ ;;; Normal Cases (ert-deftest test-wttrin--get-cached-or-fetch-normal-cache-hit-returns-cached-data () - "Test that fresh cached data is returned without fetching." + "Test that cached data is returned without fetching." (test-wttrin--get-cached-or-fetch-setup) (unwind-protect (let* ((location "Paris") @@ -44,11 +46,11 @@ (now 1000.0) (callback-result nil) (fetch-called nil)) - ;; Pre-populate cache with fresh data + ;; Pre-populate cache with data (puthash cache-key (cons now test-wttrin--get-cached-or-fetch-sample-weather) wttrin--cache) - ;; Mock time to be 100 seconds later (well within TTL of 900) + ;; Mock time to be 100 seconds later (cl-letf (((symbol-function 'float-time) (lambda () (+ now 100.0))) ((symbol-function 'wttrin-fetch-raw-string) @@ -99,14 +101,15 @@ (should (equal (cdr cached) test-wttrin--get-cached-or-fetch-new-weather))))) (test-wttrin--get-cached-or-fetch-teardown))) -(ert-deftest test-wttrin--get-cached-or-fetch-normal-expired-cache-fetches-new-data () - "Test that expired cache triggers fetch and updates cache." +(ert-deftest test-wttrin--get-cached-or-fetch-normal-old-data-still-served () + "Test that old cached data is served without fetching (no TTL expiration). +Proactive refresh keeps data fresh; on-demand reads always use cache." (test-wttrin--get-cached-or-fetch-setup) (unwind-protect (let* ((location "Tokyo") (cache-key (wttrin--make-cache-key location)) (old-time 1000.0) - (new-time (+ old-time 1000.0)) ; 1000 seconds later (> 900 TTL) + (new-time (+ old-time 10000.0)) ; Very old data (callback-result nil) (fetch-called nil)) @@ -114,81 +117,6 @@ (puthash cache-key (cons old-time test-wttrin--get-cached-or-fetch-sample-weather) wttrin--cache) - (cl-letf (((symbol-function 'float-time) - (lambda () new-time)) - ((symbol-function 'wttrin-fetch-raw-string) - (lambda (_location callback) - (setq fetch-called t) - (funcall callback test-wttrin--get-cached-or-fetch-new-weather))) - ((symbol-function 'wttrin--cleanup-cache-if-needed) - (lambda () nil))) - - (wttrin--get-cached-or-fetch - location - (lambda (data) (setq callback-result data))) - - ;; Should call fetch due to expiration - (should fetch-called) - ;; Should return new data - (should (equal callback-result test-wttrin--get-cached-or-fetch-new-weather)) - ;; Should update cache timestamp - (let ((cached (gethash cache-key wttrin--cache))) - (should (equal (car cached) new-time)) - (should (equal (cdr cached) test-wttrin--get-cached-or-fetch-new-weather))))) - (test-wttrin--get-cached-or-fetch-teardown))) - -;;; Boundary Cases - -(ert-deftest test-wttrin--get-cached-or-fetch-boundary-exactly-at-ttl-fetches () - "Test that cache exactly at TTL boundary triggers fetch." - (test-wttrin--get-cached-or-fetch-setup) - (unwind-protect - (let* ((location "Berlin") - (cache-key (wttrin--make-cache-key location)) - (old-time 1000.0) - ;; Exactly at TTL boundary (900 seconds = wttrin-cache-ttl) - (new-time (+ old-time wttrin-cache-ttl)) - (callback-result nil) - (fetch-called nil)) - - ;; Pre-populate cache - (puthash cache-key (cons old-time test-wttrin--get-cached-or-fetch-sample-weather) - wttrin--cache) - - (cl-letf (((symbol-function 'float-time) - (lambda () new-time)) - ((symbol-function 'wttrin-fetch-raw-string) - (lambda (_location callback) - (setq fetch-called t) - (funcall callback test-wttrin--get-cached-or-fetch-new-weather))) - ((symbol-function 'wttrin--cleanup-cache-if-needed) - (lambda () nil))) - - (wttrin--get-cached-or-fetch - location - (lambda (data) (setq callback-result data))) - - ;; At exactly TTL, should fetch (not <) - (should fetch-called) - (should (equal callback-result test-wttrin--get-cached-or-fetch-new-weather)))) - (test-wttrin--get-cached-or-fetch-teardown))) - -(ert-deftest test-wttrin--get-cached-or-fetch-boundary-one-second-before-ttl-uses-cache () - "Test that cache one second before TTL uses cached data." - (test-wttrin--get-cached-or-fetch-setup) - (unwind-protect - (let* ((location "Madrid") - (cache-key (wttrin--make-cache-key location)) - (old-time 1000.0) - ;; One second before TTL expiration - (new-time (+ old-time (- wttrin-cache-ttl 1))) - (callback-result nil) - (fetch-called nil)) - - ;; Pre-populate cache - (puthash cache-key (cons old-time test-wttrin--get-cached-or-fetch-sample-weather) - wttrin--cache) - (cl-letf (((symbol-function 'float-time) (lambda () new-time)) ((symbol-function 'wttrin-fetch-raw-string) @@ -199,11 +127,13 @@ location (lambda (data) (setq callback-result data))) - ;; Should use cache (still fresh) + ;; Should serve old data without fetching (should-not fetch-called) (should (equal callback-result test-wttrin--get-cached-or-fetch-sample-weather)))) (test-wttrin--get-cached-or-fetch-teardown))) +;;; Boundary Cases + (ert-deftest test-wttrin--get-cached-or-fetch-boundary-force-refresh-bypasses-fresh-cache () "Test that force refresh flag bypasses fresh cache." (test-wttrin--get-cached-or-fetch-setup) @@ -220,7 +150,7 @@ wttrin--cache) (cl-letf (((symbol-function 'float-time) - (lambda () (+ now 100.0))) ; Well within TTL + (lambda () (+ now 100.0))) ((symbol-function 'wttrin-fetch-raw-string) (lambda (_location callback) (setq fetch-called t) @@ -314,17 +244,16 @@ (unwind-protect (let* ((location "Vienna") (cache-key (wttrin--make-cache-key location)) - (old-time 1000.0) - (new-time (+ old-time 2000.0)) ; Well expired (callback-result nil) - (message-shown nil)) + (message-shown nil) + (wttrin--force-refresh t)) ; Force refresh to trigger fetch - ;; Pre-populate cache with expired data - (puthash cache-key (cons old-time test-wttrin--get-cached-or-fetch-sample-weather) + ;; Pre-populate cache with data + (puthash cache-key (cons 1000.0 test-wttrin--get-cached-or-fetch-sample-weather) wttrin--cache) (cl-letf (((symbol-function 'float-time) - (lambda () new-time)) + (lambda () 3000.0)) ((symbol-function 'wttrin-fetch-raw-string) (lambda (_location callback) ;; Simulate network failure -- cgit v1.2.3