caldav-adapter 8.3.4 → 8.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "caldav-adapter",
3
3
  "description": "CalDAV server for Node.js and Koa. Modernized and maintained for Forward Email.",
4
- "version": "8.3.4",
4
+ "version": "8.3.6",
5
5
  "author": "Sanders DeNardi and Forward Email LLC",
6
6
  "contributors": [
7
7
  "Sanders DeNardi <sedenardi@gmail.com> (http://www.sandersdenardi.com/)",
@@ -48,6 +48,8 @@ module.exports = function (options) {
48
48
  // Priority order:
49
49
  // 1. event.href - the original resource path (if stored by the data layer)
50
50
  // 2. Constructed from event.eventId - fallback for backwards compatibility
51
+ // This fallback produces the same URLs that were returned to clients
52
+ // before the href field was added, so it matches what clients cached.
51
53
  //
52
54
  // See: https://www.rfc-editor.org/rfc/rfc6578.html (sync-collection)
53
55
  //
@@ -125,6 +125,37 @@ test('event-response returns 404 status for deleted events', async (t) => {
125
125
  t.true(responseObj['D:status'].includes('404'));
126
126
  });
127
127
 
128
+ test('event-response returns 404 for deleted events without href using fallback URL', async (t) => {
129
+ const options = createMockOptions();
130
+ const eventResponse = eventResponseFactory(options);
131
+ const ctx = createMockCtx('/cal/user/calendar');
132
+ const calendar = createMockCalendar();
133
+
134
+ // Deleted event WITHOUT href - should use fallback URL construction
135
+ // This is critical for backwards compatibility with events created
136
+ // before the href field was added
137
+ const events = [
138
+ {
139
+ eventId: 'deleted-event-no-href',
140
+ deleted_at: new Date(),
141
+ ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
142
+ }
143
+ ];
144
+
145
+ const children = [];
146
+
147
+ const result = await eventResponse(ctx, events, calendar, children);
148
+
149
+ t.truthy(result.responses);
150
+ // Should NOT be skipped - fallback URL construction works for backwards compatibility
151
+ t.is(result.responses.length, 1);
152
+
153
+ const responseObj = result.responses[0];
154
+ t.is(responseObj['D:href'], '/cal/user/calendar/deleted-event-no-href.ics');
155
+ t.truthy(responseObj['D:status']);
156
+ t.true(responseObj['D:status'].includes('404'));
157
+ });
158
+
128
159
  test('event-response uses href for deleted events when available (critical for sync)', async (t) => {
129
160
  const options = createMockOptions();
130
161
  const eventResponse = eventResponseFactory(options);
@@ -217,11 +248,59 @@ test('event-response handles multiple events correctly', async (t) => {
217
248
  // Event 2: uses href
218
249
  t.is(result.responses[1]['D:href'], '/cal/user/calendar/custom-path.ics');
219
250
 
220
- // Event 3: deleted, uses eventId (no href)
251
+ // Event 3: deleted, uses eventId (no href) - fallback URL construction
221
252
  t.is(result.responses[2]['D:href'], '/cal/user/calendar/event3.ics');
222
253
  t.true(result.responses[2]['D:status'].includes('404'));
223
254
  });
224
255
 
256
+ test('event-response handles multiple events with mixed deleted states', async (t) => {
257
+ const options = createMockOptions();
258
+ const eventResponse = eventResponseFactory(options);
259
+ const ctx = createMockCtx('/cal/user/calendar');
260
+ const calendar = createMockCalendar();
261
+
262
+ // Mix of events: active, deleted with href, deleted without href
263
+ const events = [
264
+ {
265
+ eventId: 'active-event',
266
+ ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
267
+ },
268
+ {
269
+ eventId: 'deleted-with-href',
270
+ href: '/cal/user/calendar/deleted-with-href.ics',
271
+ deleted_at: new Date(),
272
+ ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
273
+ },
274
+ {
275
+ eventId: 'deleted-no-href',
276
+ deleted_at: new Date(),
277
+ ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
278
+ }
279
+ ];
280
+
281
+ const children = [];
282
+
283
+ const result = await eventResponse(ctx, events, calendar, children);
284
+
285
+ t.truthy(result.responses);
286
+ // All 3 responses - deleted without href is NO LONGER skipped
287
+ t.is(result.responses.length, 3);
288
+
289
+ // Active event
290
+ t.is(result.responses[0]['D:href'], '/cal/user/calendar/active-event.ics');
291
+
292
+ // Deleted event with href
293
+ t.is(
294
+ result.responses[1]['D:href'],
295
+ '/cal/user/calendar/deleted-with-href.ics'
296
+ );
297
+ t.true(result.responses[1]['D:status'].includes('404'));
298
+
299
+ // Deleted event without href - uses fallback URL construction
300
+ t.is(result.responses[2]['D:href'], '/cal/user/calendar/deleted-no-href.ics');
301
+ t.true(result.responses[2]['D:status'].includes('404'));
302
+ });
303
+
225
304
  test('event-response handles email-like eventId with @ symbol', async (t) => {
226
305
  const options = createMockOptions();
227
306
  const eventResponse = eventResponseFactory(options);