summaryrefslogtreecommitdiff
path: root/tests/test-calendar-sync--collect-recurrence-exceptions.el
blob: d2567b3ad2b82743d26a989750dcba6f43058b56 (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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
;;; test-calendar-sync--collect-recurrence-exceptions.el --- Tests for exception collection  -*- lexical-binding: t; -*-

;;; Commentary:
;; Unit tests for calendar-sync--collect-recurrence-exceptions function.
;; Tests collecting all RECURRENCE-ID events from ICS content.
;; 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-exception-event (uid recurrence-id summary start end)
  "Create a VEVENT with RECURRENCE-ID for testing.
UID is the base event UID. RECURRENCE-ID is the original occurrence date.
SUMMARY, START, END define the exception event."
  (concat "BEGIN:VEVENT\n"
          "UID:" uid "\n"
          "RECURRENCE-ID:" recurrence-id "\n"
          "SUMMARY:" summary "\n"
          "DTSTART:" (test-calendar-sync-ics-datetime start) "\n"
          "DTEND:" (test-calendar-sync-ics-datetime end) "\n"
          "END:VEVENT"))

(defun test-make-base-event-with-rrule (uid summary start end rrule)
  "Create a recurring VEVENT with RRULE for testing."
  (concat "BEGIN:VEVENT\n"
          "UID:" uid "\n"
          "SUMMARY:" summary "\n"
          "DTSTART:" (test-calendar-sync-ics-datetime start) "\n"
          "DTEND:" (test-calendar-sync-ics-datetime end) "\n"
          "RRULE:" rrule "\n"
          "END:VEVENT"))

;;; Normal Cases

(ert-deftest test-calendar-sync--collect-recurrence-exceptions-normal-single-exception ()
  "Test collecting single exception event."
  (let* ((start (test-calendar-sync-time-days-from-now 7 10 0))
         (end (test-calendar-sync-time-days-from-now 7 11 0))
         (ics (test-calendar-sync-make-ics
               (test-make-exception-event "event123@google.com"
                                          "20260203T090000Z"
                                          "Rescheduled Meeting"
                                          start end)))
         (exceptions (calendar-sync--collect-recurrence-exceptions ics)))
    ;; Should have one exception keyed by UID
    (should (hash-table-p exceptions))
    (should (gethash "event123@google.com" exceptions))))

(ert-deftest test-calendar-sync--collect-recurrence-exceptions-normal-multiple-same-uid ()
  "Test collecting multiple exceptions for same recurring event."
  (let* ((start1 (test-calendar-sync-time-days-from-now 7 10 0))
         (end1 (test-calendar-sync-time-days-from-now 7 11 0))
         (start2 (test-calendar-sync-time-days-from-now 14 11 0))
         (end2 (test-calendar-sync-time-days-from-now 14 12 0))
         (ics (test-calendar-sync-make-ics
               (test-make-exception-event "weekly123@google.com"
                                          "20260203T090000Z"
                                          "Week 1 Rescheduled"
                                          start1 end1)
               (test-make-exception-event "weekly123@google.com"
                                          "20260210T090000Z"
                                          "Week 2 Rescheduled"
                                          start2 end2)))
         (exceptions (calendar-sync--collect-recurrence-exceptions ics)))
    ;; Should have list of exceptions under single UID
    (should (hash-table-p exceptions))
    (let ((uid-exceptions (gethash "weekly123@google.com" exceptions)))
      (should uid-exceptions)
      (should (= 2 (length uid-exceptions))))))

(ert-deftest test-calendar-sync--collect-recurrence-exceptions-normal-multiple-uids ()
  "Test collecting exceptions for different recurring events."
  (let* ((start (test-calendar-sync-time-days-from-now 7 10 0))
         (end (test-calendar-sync-time-days-from-now 7 11 0))
         (ics (test-calendar-sync-make-ics
               (test-make-exception-event "event-a@google.com"
                                          "20260203T090000Z"
                                          "Event A Rescheduled"
                                          start end)
               (test-make-exception-event "event-b@google.com"
                                          "20260203T140000Z"
                                          "Event B Rescheduled"
                                          start end)))
         (exceptions (calendar-sync--collect-recurrence-exceptions ics)))
    ;; Should have two UIDs
    (should (hash-table-p exceptions))
    (should (gethash "event-a@google.com" exceptions))
    (should (gethash "event-b@google.com" exceptions))))

;;; Boundary Cases

(ert-deftest test-calendar-sync--collect-recurrence-exceptions-boundary-no-exceptions ()
  "Test ICS with no RECURRENCE-ID events returns empty hash."
  (let* ((start (test-calendar-sync-time-days-from-now 7 10 0))
         (end (test-calendar-sync-time-days-from-now 7 11 0))
         (ics (test-calendar-sync-make-ics
               (test-calendar-sync-make-vevent "Regular Event" start end)))
         (exceptions (calendar-sync--collect-recurrence-exceptions ics)))
    (should (hash-table-p exceptions))
    (should (= 0 (hash-table-count exceptions)))))

(ert-deftest test-calendar-sync--collect-recurrence-exceptions-boundary-mixed-events ()
  "Test ICS with both regular and exception events."
  (let* ((start (test-calendar-sync-time-days-from-now 7 10 0))
         (end (test-calendar-sync-time-days-from-now 7 11 0))
         (base-start (test-calendar-sync-time-days-from-now 1 9 0))
         (base-end (test-calendar-sync-time-days-from-now 1 10 0))
         (ics (test-calendar-sync-make-ics
               ;; Regular event (no RECURRENCE-ID)
               (test-calendar-sync-make-vevent "Normal Meeting" base-start base-end)
               ;; Base recurring event with RRULE
               (test-make-base-event-with-rrule "recurring@google.com"
                                                 "Weekly Sync"
                                                 base-start base-end
                                                 "FREQ=WEEKLY;COUNT=10")
               ;; Exception to the recurring event
               (test-make-exception-event "recurring@google.com"
                                          "20260210T090000Z"
                                          "Weekly Sync (Rescheduled)"
                                          start end)))
         (exceptions (calendar-sync--collect-recurrence-exceptions ics)))
    ;; Should only collect the exception, not regular or base events
    (should (hash-table-p exceptions))
    (should (= 1 (hash-table-count exceptions)))
    (should (gethash "recurring@google.com" exceptions))))

(ert-deftest test-calendar-sync--collect-recurrence-exceptions-boundary-tzid-exception ()
  "Test exception event with TZID-qualified RECURRENCE-ID."
  (let* ((start (test-calendar-sync-time-days-from-now 7 10 0))
         (end (test-calendar-sync-time-days-from-now 7 11 0))
         (event (concat "BEGIN:VEVENT\n"
                        "UID:tzid-event@google.com\n"
                        "RECURRENCE-ID;TZID=Europe/Tallinn:20260203T170000\n"
                        "SUMMARY:Tallinn Meeting Rescheduled\n"
                        "DTSTART:" (test-calendar-sync-ics-datetime start) "\n"
                        "DTEND:" (test-calendar-sync-ics-datetime end) "\n"
                        "END:VEVENT"))
         (ics (test-calendar-sync-make-ics event))
         (exceptions (calendar-sync--collect-recurrence-exceptions ics)))
    (should (hash-table-p exceptions))
    (should (gethash "tzid-event@google.com" exceptions))))

;;; Error Cases

(ert-deftest test-calendar-sync--collect-recurrence-exceptions-error-empty-string ()
  "Test empty ICS content returns empty hash."
  (let ((exceptions (calendar-sync--collect-recurrence-exceptions "")))
    (should (hash-table-p exceptions))
    (should (= 0 (hash-table-count exceptions)))))

(ert-deftest test-calendar-sync--collect-recurrence-exceptions-error-nil-input ()
  "Test nil input returns empty hash or nil."
  (let ((exceptions (calendar-sync--collect-recurrence-exceptions nil)))
    (should (or (null exceptions)
                (and (hash-table-p exceptions)
                     (= 0 (hash-table-count exceptions)))))))

(ert-deftest test-calendar-sync--collect-recurrence-exceptions-error-malformed-ics ()
  "Test malformed ICS handles gracefully."
  (let ((exceptions (calendar-sync--collect-recurrence-exceptions "not valid ics content")))
    ;; Should not crash, return empty hash
    (should (or (null exceptions)
                (and (hash-table-p exceptions)
                     (= 0 (hash-table-count exceptions)))))))

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