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
|
+
"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);
|