summaryrefslogtreecommitdiff
path: root/tests/test-calendar-sync--apply-recurrence-exceptions.el
blob: 7711c5cb657c911772cdcdf4208cf2c16e5ba61a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
;;; test-calendar-sync--apply-recurrence-exceptions.el --- Tests for applying exceptions  -*- lexical-binding: t; -*-

;;; Commentary:
;; Unit tests for calendar-sync--apply-recurrence-exceptions function.
;; Tests applying RECURRENCE-ID exceptions to expanded occurrences.
;; Following quality-engineer.org guidelines: one function per file.

;;; Code:

(require 'ert)
(add-to-list 'load-path (expand-file-name "." (file-name-directory load-file-name)))
(add-to-list 'load-path (expand-file-name "../modules" (file-name-directory load-file-name)))
(require 'testutil-calendar-sync)
(require 'calendar-sync)

;;; Test Data Helpers

(defun test-make-occurrence (year month day hour minute summary &optional uid)
  "Create an occurrence plist for testing.
Mirrors the format used by calendar-sync expansion functions.
Uses :start (year month day hour minute) format."
  (list :start (list year month day hour minute)
        :end (list year month day (1+ hour) minute)
        :summary summary
        :uid (or uid "test-event@google.com")))

(defun test-make-exception-data (rec-year rec-month rec-day rec-hour rec-minute
                                  new-year new-month new-day new-hour new-minute
                                  &optional summary)
  "Create exception data plist for testing.
REC-* are the original occurrence date/time (recurrence-id).
NEW-* values are the rescheduled time."
  (list :recurrence-id (list rec-year rec-month rec-day rec-hour rec-minute)
        :start (list new-year new-month new-day new-hour new-minute)
        :end (list new-year new-month new-day (1+ new-hour) new-minute)
        :summary (or summary "Rescheduled")))

;;; Normal Cases

(ert-deftest test-calendar-sync--apply-recurrence-exceptions-normal-single-replacement ()
  "Test replacing single occurrence with exception."
  (let* ((occurrences (list (test-make-occurrence 2026 2 3 9 0 "Weekly Meeting")))
         (exceptions (make-hash-table :test 'equal)))
    ;; Exception: Feb 3 at 9:00 → Feb 3 at 10:00
    (puthash "test-event@google.com"
             (list (test-make-exception-data 2026 2 3 9 0  2026 2 3 10 0))
             exceptions)
    (let ((result (calendar-sync--apply-recurrence-exceptions occurrences exceptions)))
      (should (= 1 (length result)))
      ;; Time should be updated to 10:00
      (should (= 10 (nth 3 (plist-get (car result) :start)))))))

(ert-deftest test-calendar-sync--apply-recurrence-exceptions-normal-multiple-some-replaced ()
  "Test multiple occurrences with one exception."
  (let* ((occurrences (list (test-make-occurrence 2026 2 3 9 0 "Weekly Meeting")
                            (test-make-occurrence 2026 2 10 9 0 "Weekly Meeting")
                            (test-make-occurrence 2026 2 17 9 0 "Weekly Meeting")))
         (exceptions (make-hash-table :test 'equal)))
    ;; Only Feb 10 is rescheduled
    (puthash "test-event@google.com"
             (list (test-make-exception-data 2026 2 10 9 0  2026 2 10 11 0))
             exceptions)
    (let ((result (calendar-sync--apply-recurrence-exceptions occurrences exceptions)))
      (should (= 3 (length result)))
      ;; Feb 3: unchanged (9:00)
      (should (= 9 (nth 3 (plist-get (nth 0 result) :start))))
      ;; Feb 10: rescheduled (11:00)
      (should (= 11 (nth 3 (plist-get (nth 1 result) :start))))
      ;; Feb 17: unchanged (9:00)
      (should (= 9 (nth 3 (plist-get (nth 2 result) :start)))))))

(ert-deftest test-calendar-sync--apply-recurrence-exceptions-normal-date-change ()
  "Test exception that changes the date, not just time."
  (let* ((occurrences (list (test-make-occurrence 2026 2 3 9 0 "Weekly Meeting")))
         (exceptions (make-hash-table :test 'equal)))
    ;; Feb 3 rescheduled to Feb 4
    (puthash "test-event@google.com"
             (list (test-make-exception-data 2026 2 3 9 0  2026 2 4 10 0))
             exceptions)
    (let ((result (calendar-sync--apply-recurrence-exceptions occurrences exceptions)))
      (should (= 1 (length result)))
      (let ((new-start (plist-get (car result) :start)))
        (should (= 4 (nth 2 new-start)))   ; day
        (should (= 10 (nth 3 new-start))))))) ; hour

;;; Boundary Cases

(ert-deftest test-calendar-sync--apply-recurrence-exceptions-boundary-no-exceptions ()
  "Test occurrences returned unchanged when no exceptions."
  (let* ((occurrences (list (test-make-occurrence 2026 2 3 9 0 "Weekly Meeting")
                            (test-make-occurrence 2026 2 10 9 0 "Weekly Meeting")))
         (exceptions (make-hash-table :test 'equal)))
    (let ((result (calendar-sync--apply-recurrence-exceptions occurrences exceptions)))
      (should (= 2 (length result)))
      (should (= 9 (nth 3 (plist-get (nth 0 result) :start))))
      (should (= 9 (nth 3 (plist-get (nth 1 result) :start)))))))

(ert-deftest test-calendar-sync--apply-recurrence-exceptions-boundary-exception-no-match ()
  "Test exception for different UID doesn't affect occurrences."
  (let* ((occurrences (list (test-make-occurrence 2026 2 3 9 0 "Weekly Meeting" "event-a@google.com")))
         (exceptions (make-hash-table :test 'equal)))
    ;; Exception is for different UID
    (puthash "event-b@google.com"
             (list (test-make-exception-data 2026 2 3 9 0  2026 2 3 11 0))
             exceptions)
    (let ((result (calendar-sync--apply-recurrence-exceptions occurrences exceptions)))
      (should (= 1 (length result)))
      ;; Should remain at 9:00, not 11:00
      (should (= 9 (nth 3 (plist-get (car result) :start)))))))

(ert-deftest test-calendar-sync--apply-recurrence-exceptions-boundary-multiple-uids ()
  "Test exceptions for multiple different recurring events."
  (let* ((occurrences (list (test-make-occurrence 2026 2 3 9 0 "Meeting A" "event-a@google.com")
                            (test-make-occurrence 2026 2 3 14 0 "Meeting B" "event-b@google.com")))
         (exceptions (make-hash-table :test 'equal)))
    ;; Different exceptions for each UID
    (puthash "event-a@google.com"
             (list (test-make-exception-data 2026 2 3 9 0  2026 2 3 10 0))
             exceptions)
    (puthash "event-b@google.com"
             (list (test-make-exception-data 2026 2 3 14 0  2026 2 3 15 0))
             exceptions)
    (let ((result (calendar-sync--apply-recurrence-exceptions occurrences exceptions)))
      (should (= 2 (length result)))
      ;; Each should have its respective exception applied
      (should (= 10 (nth 3 (plist-get (nth 0 result) :start))))
      (should (= 15 (nth 3 (plist-get (nth 1 result) :start)))))))

(ert-deftest test-calendar-sync--apply-recurrence-exceptions-boundary-empty-occurrences ()
  "Test empty occurrences list returns empty."
  (let* ((occurrences '())
         (exceptions (make-hash-table :test 'equal)))
    (puthash "test@google.com"
             (list (test-make-exception-data 2026 2 3 9 0  2026 2 3 10 0))
             exceptions)
    (let ((result (calendar-sync--apply-recurrence-exceptions occurrences exceptions)))
      (should (= 0 (length result))))))

;;; Error Cases

(ert-deftest test-calendar-sync--apply-recurrence-exceptions-error-nil-occurrences ()
  "Test nil occurrences handled gracefully."
  (let ((exceptions (make-hash-table :test 'equal)))
    (let ((result (calendar-sync--apply-recurrence-exceptions nil exceptions)))
      (should (or (null result) (= 0 (length result)))))))

(ert-deftest test-calendar-sync--apply-recurrence-exceptions-error-nil-exceptions ()
  "Test nil exceptions handled gracefully."
  (let ((occurrences (list (test-make-occurrence 2026 2 3 9 0 "Meeting"))))
    (let ((result (calendar-sync--apply-recurrence-exceptions occurrences nil)))
      ;; Should return occurrences unchanged or handle nil
      (should (or (null result)
                  (and (= 1 (length result))
                       (= 9 (nth 3 (plist-get (car result) :start)))))))))

(provide 'test-calendar-sync--apply-recurrence-exceptions)
;;; test-calendar-sync--apply-recurrence-exceptions.el ends here