aboutsummaryrefslogtreecommitdiff
path: root/tests/test-chime-validation-retry.el
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test-chime-validation-retry.el')
-rw-r--r--tests/test-chime-validation-retry.el435
1 files changed, 435 insertions, 0 deletions
diff --git a/tests/test-chime-validation-retry.el b/tests/test-chime-validation-retry.el
new file mode 100644
index 0000000..b35fd29
--- /dev/null
+++ b/tests/test-chime-validation-retry.el
@@ -0,0 +1,435 @@
+;;; test-chime-validation-retry.el --- Tests for chime validation retry logic -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for chime's configuration validation retry mechanism.
+;;
+;; Tests cover the graceful retry behavior when org-agenda-files is not
+;; immediately available (e.g., loaded asynchronously via idle timer).
+;;
+;; The retry mechanism allows chime to wait for org-agenda-files to be
+;; populated before showing configuration errors, providing a better UX
+;; for users with async initialization code.
+;;
+;; Components tested:
+;; - chime--validation-retry-count tracking
+;; - chime-validation-max-retries configuration
+;; - chime-check validation retry logic
+;; - chime--stop retry counter reset
+;; - Message display behavior (waiting vs error)
+
+;;; Code:
+
+(require 'ert)
+
+;; Initialize package system to make installed packages available in batch mode
+(require 'package)
+(setq package-user-dir (expand-file-name "~/.emacs.d/elpa"))
+(package-initialize)
+
+;; Load chime from parent directory (which will load its dependencies)
+(load (expand-file-name "../chime.el") nil t)
+
+;;; Setup and Teardown
+
+(defvar test-chime-validation-retry--original-max-retries nil
+ "Original value of chime-validation-max-retries for restoration.")
+
+(defvar test-chime-validation-retry--original-agenda-files nil
+ "Original value of org-agenda-files for restoration.")
+
+(defun test-chime-validation-retry-setup ()
+ "Set up test environment before each test."
+ ;; Save original values
+ (setq test-chime-validation-retry--original-max-retries chime-validation-max-retries)
+ (setq test-chime-validation-retry--original-agenda-files org-agenda-files)
+
+ ;; Reset validation state
+ (setq chime--validation-done nil)
+ (setq chime--validation-retry-count 0)
+
+ ;; Set predictable defaults
+ (setq chime-validation-max-retries 3))
+
+(defun test-chime-validation-retry-teardown ()
+ "Clean up test environment after each test."
+ ;; Restore original values
+ (setq chime-validation-max-retries test-chime-validation-retry--original-max-retries)
+ (setq org-agenda-files test-chime-validation-retry--original-agenda-files)
+
+ ;; Reset validation state
+ (setq chime--validation-done nil)
+ (setq chime--validation-retry-count 0))
+
+;;; Normal Cases - Retry Behavior
+
+(ert-deftest test-chime-validation-retry-normal-first-failure-shows-waiting ()
+ "Test first validation failure shows waiting message, not error.
+
+When org-agenda-files is empty on the first check, chime should show
+a friendly waiting message instead of immediately displaying the full
+error. This accommodates async org-agenda-files initialization."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ ;; Empty org-agenda-files to trigger validation failure
+ (setq org-agenda-files nil)
+
+ ;; Capture message output
+ (let ((messages nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages)))
+ ;; Mock fetch to prevent actual agenda processing
+ ((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil)))
+
+ ;; Call chime-check
+ (chime-check)
+
+ ;; Should show waiting message
+ (should (= chime--validation-retry-count 1))
+ (should-not chime--validation-done)
+ (should (cl-some (lambda (msg)
+ (string-match-p "Waiting for org-agenda-files" msg))
+ messages))
+ ;; Should NOT show error message
+ (should-not (cl-some (lambda (msg)
+ (string-match-p "Configuration errors detected" msg))
+ messages)))))
+ (test-chime-validation-retry-teardown)))
+
+(ert-deftest test-chime-validation-retry-normal-success-resets-counter ()
+ "Test successful validation after retry resets counter to zero.
+
+When validation succeeds on a retry attempt, the retry counter should
+be reset to 0, allowing fresh retry attempts if validation fails again
+later (e.g., after mode restart)."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ ;; Simulate one failed attempt
+ (setq chime--validation-retry-count 1)
+
+ ;; Set valid org-agenda-files
+ (setq org-agenda-files '("/tmp/test.org"))
+
+ ;; Mock fetch to prevent actual agenda processing
+ (cl-letf (((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil)))
+
+ ;; Call chime-check - should succeed
+ (chime-check)
+
+ ;; Counter should be reset
+ (should (= chime--validation-retry-count 0))
+ ;; Validation marked as done
+ (should chime--validation-done)))
+ (test-chime-validation-retry-teardown)))
+
+(ert-deftest test-chime-validation-retry-normal-multiple-retries-increment ()
+ "Test multiple validation failures increment counter correctly.
+
+Each validation failure should increment the retry counter by 1,
+allowing the system to track how many retries have been attempted."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ ;; Empty org-agenda-files
+ (setq org-agenda-files nil)
+
+ ;; Mock fetch
+ (cl-letf (((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil))
+ ((symbol-function 'message)
+ (lambda (&rest args) nil)))
+
+ ;; First attempt
+ (chime-check)
+ (should (= chime--validation-retry-count 1))
+
+ ;; Second attempt
+ (chime-check)
+ (should (= chime--validation-retry-count 2))
+
+ ;; Third attempt
+ (chime-check)
+ (should (= chime--validation-retry-count 3))))
+ (test-chime-validation-retry-teardown)))
+
+(ert-deftest test-chime-validation-retry-normal-successful-validation-proceeds ()
+ "Test successful validation proceeds with event checking.
+
+When validation passes, chime-check should proceed to fetch and
+process events normally."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ ;; Valid org-agenda-files
+ (setq org-agenda-files '("/tmp/test.org"))
+
+ ;; Track if fetch was called
+ (let ((fetch-called nil))
+ (cl-letf (((symbol-function 'chime--fetch-and-process)
+ (lambda (callback)
+ (setq fetch-called t))))
+
+ ;; Call chime-check
+ (chime-check)
+
+ ;; Should proceed to fetch
+ (should fetch-called)
+ (should chime--validation-done)
+ (should (= chime--validation-retry-count 0)))))
+ (test-chime-validation-retry-teardown)))
+
+;;; Boundary Cases - Edge Conditions
+
+(ert-deftest test-chime-validation-retry-boundary-max-retries-zero ()
+ "Test max-retries=0 shows error immediately without retrying.
+
+When chime-validation-max-retries is set to 0, validation failures
+should immediately show the full error message without any retry
+attempts."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ ;; Set max retries to 0
+ (setq chime-validation-max-retries 0)
+
+ ;; Empty org-agenda-files
+ (setq org-agenda-files nil)
+
+ ;; Capture message output
+ (let ((messages nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages)))
+ ((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil)))
+
+ ;; Call chime-check
+ (chime-check)
+
+ ;; Counter incremented
+ (should (= chime--validation-retry-count 1))
+ ;; Should show error, not waiting message
+ (should (cl-some (lambda (msg)
+ (string-match-p "Configuration errors detected" msg))
+ messages))
+ (should-not (cl-some (lambda (msg)
+ (string-match-p "Waiting for" msg))
+ messages)))))
+ (test-chime-validation-retry-teardown)))
+
+(ert-deftest test-chime-validation-retry-boundary-max-retries-one ()
+ "Test max-retries=1 allows one retry before showing error.
+
+First attempt should show waiting message, second attempt should
+show full error."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ ;; Set max retries to 1
+ (setq chime-validation-max-retries 1)
+
+ ;; Empty org-agenda-files
+ (setq org-agenda-files nil)
+
+ (cl-letf (((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil)))
+
+ ;; First attempt - should show waiting
+ (let ((messages nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime-check)
+ (should (= chime--validation-retry-count 1))
+ (should (cl-some (lambda (msg)
+ (string-match-p "Waiting for" msg))
+ messages))))
+
+ ;; Second attempt - should show error
+ (let ((messages nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime-check)
+ (should (= chime--validation-retry-count 2))
+ (should (cl-some (lambda (msg)
+ (string-match-p "Configuration errors detected" msg))
+ messages))))))
+ (test-chime-validation-retry-teardown)))
+
+(ert-deftest test-chime-validation-retry-boundary-exactly-at-threshold ()
+ "Test behavior exactly at max-retries threshold.
+
+The (retry_count + 1)th attempt should show the error message."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ ;; Default max retries = 3
+ (setq chime-validation-max-retries 3)
+ (setq org-agenda-files nil)
+
+ (cl-letf (((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil)))
+
+ ;; Attempts 1-3: waiting messages
+ (dotimes (_ 3)
+ (let ((messages nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime-check)
+ (should (cl-some (lambda (msg)
+ (string-match-p "Waiting for" msg))
+ messages)))))
+
+ ;; Attempt 4: should show error
+ (let ((messages nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime-check)
+ (should (= chime--validation-retry-count 4))
+ (should (cl-some (lambda (msg)
+ (string-match-p "Configuration errors detected" msg))
+ messages))))))
+ (test-chime-validation-retry-teardown)))
+
+(ert-deftest test-chime-validation-retry-boundary-stop-resets-counter ()
+ "Test chime--stop resets retry counter to zero.
+
+When chime-mode is stopped, the retry counter should be reset to
+allow fresh retry attempts on next start."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ ;; Simulate some failed attempts
+ (setq chime--validation-retry-count 5)
+ (setq chime--validation-done nil)
+
+ ;; Call stop
+ (chime--stop)
+
+ ;; Counter should be reset
+ (should (= chime--validation-retry-count 0))
+ (should-not chime--validation-done))
+ (test-chime-validation-retry-teardown)))
+
+(ert-deftest test-chime-validation-retry-boundary-empty-agenda-files ()
+ "Test empty org-agenda-files list triggers retry.
+
+An empty list should be treated the same as nil - both should
+trigger validation failure and retry."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ ;; Empty list (not nil)
+ (setq org-agenda-files '())
+
+ (let ((messages nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages)))
+ ((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil)))
+
+ ;; Should trigger retry
+ (chime-check)
+ (should (= chime--validation-retry-count 1))
+ (should (cl-some (lambda (msg)
+ (string-match-p "Waiting for" msg))
+ messages)))))
+ (test-chime-validation-retry-teardown)))
+
+;;; Error Cases - Failure Scenarios
+
+(ert-deftest test-chime-validation-retry-error-exceeding-max-shows-full-error ()
+ "Test exceeding max retries shows full error with details.
+
+After max retries exceeded, the full validation error should be
+displayed with all error details in the *Messages* buffer."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ (setq chime-validation-max-retries 2)
+ (setq org-agenda-files nil)
+
+ (cl-letf (((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil)))
+
+ ;; Exhaust retries
+ (dotimes (_ 3)
+ (let ((messages nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime-check))))
+
+ ;; Verify error message on next attempt
+ (let ((messages nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (push (apply #'format format-string args) messages))))
+ (chime-check)
+ ;; Should show error message (detailed error with retry count goes to *Messages* buffer via chime--log-silently)
+ (should (cl-some (lambda (msg)
+ (string-match-p "Configuration errors detected" msg))
+ messages))))))
+ (test-chime-validation-retry-teardown)))
+
+(ert-deftest test-chime-validation-retry-error-persistent-failure ()
+ "Test validation failure persisting through all retries.
+
+If org-agenda-files remains empty through all retry attempts,
+validation should never be marked as done."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ (setq chime-validation-max-retries 3)
+ (setq org-agenda-files nil)
+
+ (cl-letf (((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil))
+ ((symbol-function 'message)
+ (lambda (&rest args) nil)))
+
+ ;; Multiple attempts, all failing
+ (dotimes (_ 10)
+ (chime-check)
+ ;; Should never mark as done
+ (should-not chime--validation-done))
+
+ ;; Counter keeps incrementing
+ (should (= chime--validation-retry-count 10))))
+ (test-chime-validation-retry-teardown)))
+
+(ert-deftest test-chime-validation-retry-error-counter-large-value ()
+ "Test retry counter handles large values without overflow.
+
+The retry counter should continue incrementing correctly even with
+many retry attempts, ensuring no integer overflow issues."
+ (test-chime-validation-retry-setup)
+ (unwind-protect
+ (progn
+ (setq chime-validation-max-retries 1000)
+ (setq org-agenda-files nil)
+
+ (cl-letf (((symbol-function 'chime--fetch-and-process)
+ (lambda (callback) nil))
+ ((symbol-function 'message)
+ (lambda (&rest args) nil)))
+
+ ;; Many attempts
+ (dotimes (i 100)
+ (chime-check)
+ (should (= chime--validation-retry-count (1+ i))))
+
+ ;; Should still be counting correctly
+ (should (= chime--validation-retry-count 100))))
+ (test-chime-validation-retry-teardown)))
+
+(provide 'test-chime-validation-retry)
+;;; test-chime-validation-retry.el ends here