caldav-adapter 9.2.0 → 9.3.0
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/common/xml-encode.js +24 -0
- package/index.js +18 -10
- package/package.json +7 -2
- package/routes/calendar/calendar/calendar-multiget.js +5 -1
- package/routes/calendar/calendar/delete.js +1 -1
- package/routes/calendar/calendar/propfind.js +11 -0
- package/routes/calendar/calendar/put.js +19 -1
- package/routes/calendar/calendar/report.js +4 -1
- package/routes/calendar/calendar/sync-collection.js +18 -1
- package/routes/calendar/calendar.js +11 -4
- package/routes/calendar/scheduling.js +14 -6
- package/routes/calendar/user/propfind.js +10 -0
- package/routes/principal/mkcalendar.js +57 -34
- package/routes/principal/propfind.js +24 -7
- package/routes/principal/report.js +6 -0
- package/.commitlintrc.js +0 -3
- package/.editorconfig +0 -9
- package/.gitattributes +0 -1
- package/.github/workflows/ci.yml +0 -24
- package/.husky/commit-msg +0 -4
- package/.husky/pre-commit +0 -4
- package/.lintstagedrc.js +0 -5
- package/.prettierrc.js +0 -5
- package/.remarkignore +0 -1
- package/.remarkrc.js +0 -3
- package/.xo-config.js +0 -8
- package/test/event-response.test.js +0 -383
- package/test/proppatch.test.js +0 -1254
- package/test/scheduling.test.js +0 -697
- package/test/test.js +0 -41
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
const test = require('ava');
|
|
2
|
-
const eventResponseFactory = require('../routes/calendar/calendar/event-response');
|
|
3
|
-
|
|
4
|
-
// Mock options with minimal required data functions
|
|
5
|
-
function createMockOptions() {
|
|
6
|
-
return {
|
|
7
|
-
data: {
|
|
8
|
-
getCalendarId: () => 'test-calendar-id',
|
|
9
|
-
buildICS: () => 'BEGIN:VCALENDAR\nEND:VCALENDAR',
|
|
10
|
-
getETag: () => '"test-etag"'
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Mock context
|
|
16
|
-
function createMockCtx(url = '/cal/user/calendar') {
|
|
17
|
-
return {
|
|
18
|
-
url,
|
|
19
|
-
state: {
|
|
20
|
-
params: {
|
|
21
|
-
principalId: 'user',
|
|
22
|
-
calendarId: 'calendar'
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Mock calendar
|
|
29
|
-
function createMockCalendar() {
|
|
30
|
-
return {
|
|
31
|
-
_id: 'calendar-id',
|
|
32
|
-
name: 'Test Calendar',
|
|
33
|
-
synctoken: 'http://example.com/sync/1'
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
test('event-response exports a function', (t) => {
|
|
38
|
-
const options = createMockOptions();
|
|
39
|
-
const eventResponse = eventResponseFactory(options);
|
|
40
|
-
t.is(typeof eventResponse, 'function');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('event-response uses eventId to construct URL when href is not available', async (t) => {
|
|
44
|
-
const options = createMockOptions();
|
|
45
|
-
const eventResponse = eventResponseFactory(options);
|
|
46
|
-
const ctx = createMockCtx('/cal/user/calendar');
|
|
47
|
-
const calendar = createMockCalendar();
|
|
48
|
-
|
|
49
|
-
const events = [
|
|
50
|
-
{
|
|
51
|
-
eventId: 'test-event-123',
|
|
52
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
53
|
-
}
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
// Empty children array - we just want to test URL construction
|
|
57
|
-
const children = [];
|
|
58
|
-
|
|
59
|
-
const result = await eventResponse(ctx, events, calendar, children);
|
|
60
|
-
|
|
61
|
-
t.truthy(result.responses);
|
|
62
|
-
t.is(result.responses.length, 1);
|
|
63
|
-
|
|
64
|
-
// Check that the response contains the correct URL
|
|
65
|
-
const responseObj = result.responses[0];
|
|
66
|
-
t.truthy(responseObj['D:href']);
|
|
67
|
-
t.is(responseObj['D:href'], '/cal/user/calendar/test-event-123.ics');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('event-response uses event.href when available instead of constructing from eventId', async (t) => {
|
|
71
|
-
const options = createMockOptions();
|
|
72
|
-
const eventResponse = eventResponseFactory(options);
|
|
73
|
-
const ctx = createMockCtx('/cal/user/calendar');
|
|
74
|
-
const calendar = createMockCalendar();
|
|
75
|
-
|
|
76
|
-
// Event with href property (original resource path)
|
|
77
|
-
const events = [
|
|
78
|
-
{
|
|
79
|
-
eventId: 'modified_event_id',
|
|
80
|
-
href: '/cal/user/calendar/original@event.ics',
|
|
81
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
82
|
-
}
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
const children = [];
|
|
86
|
-
|
|
87
|
-
const result = await eventResponse(ctx, events, calendar, children);
|
|
88
|
-
|
|
89
|
-
t.truthy(result.responses);
|
|
90
|
-
t.is(result.responses.length, 1);
|
|
91
|
-
|
|
92
|
-
// Check that the response uses href, not eventId
|
|
93
|
-
const responseObj = result.responses[0];
|
|
94
|
-
t.truthy(responseObj['D:href']);
|
|
95
|
-
t.is(responseObj['D:href'], '/cal/user/calendar/original@event.ics');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('event-response returns 404 status for deleted events', async (t) => {
|
|
99
|
-
const options = createMockOptions();
|
|
100
|
-
const eventResponse = eventResponseFactory(options);
|
|
101
|
-
const ctx = createMockCtx('/cal/user/calendar');
|
|
102
|
-
const calendar = createMockCalendar();
|
|
103
|
-
|
|
104
|
-
const events = [
|
|
105
|
-
{
|
|
106
|
-
eventId: 'deleted-event',
|
|
107
|
-
deleted_at: new Date(),
|
|
108
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
109
|
-
}
|
|
110
|
-
];
|
|
111
|
-
|
|
112
|
-
const children = [];
|
|
113
|
-
|
|
114
|
-
const result = await eventResponse(ctx, events, calendar, children);
|
|
115
|
-
|
|
116
|
-
t.truthy(result.responses);
|
|
117
|
-
t.is(result.responses.length, 1);
|
|
118
|
-
|
|
119
|
-
const responseObj = result.responses[0];
|
|
120
|
-
t.truthy(responseObj['D:href']);
|
|
121
|
-
t.is(responseObj['D:href'], '/cal/user/calendar/deleted-event.ics');
|
|
122
|
-
|
|
123
|
-
// Check for 404 status
|
|
124
|
-
t.truthy(responseObj['D:status']);
|
|
125
|
-
t.true(responseObj['D:status'].includes('404'));
|
|
126
|
-
});
|
|
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
|
-
|
|
159
|
-
test('event-response uses href for deleted events when available (critical for sync)', async (t) => {
|
|
160
|
-
const options = createMockOptions();
|
|
161
|
-
const eventResponse = eventResponseFactory(options);
|
|
162
|
-
const ctx = createMockCtx('/cal/user/calendar');
|
|
163
|
-
const calendar = createMockCalendar();
|
|
164
|
-
|
|
165
|
-
// Deleted event with href - simulates the case where eventId was modified
|
|
166
|
-
// (e.g., @ replaced with _) but we stored the original href
|
|
167
|
-
const events = [
|
|
168
|
-
{
|
|
169
|
-
eventId: 'event123_example.com',
|
|
170
|
-
href: '/cal/user/calendar/event123@example.com.ics',
|
|
171
|
-
deleted_at: new Date(),
|
|
172
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
173
|
-
}
|
|
174
|
-
];
|
|
175
|
-
|
|
176
|
-
const children = [];
|
|
177
|
-
|
|
178
|
-
const result = await eventResponse(ctx, events, calendar, children);
|
|
179
|
-
|
|
180
|
-
t.truthy(result.responses);
|
|
181
|
-
t.is(result.responses.length, 1);
|
|
182
|
-
|
|
183
|
-
const responseObj = result.responses[0];
|
|
184
|
-
// Should use href, not eventId
|
|
185
|
-
t.is(responseObj['D:href'], '/cal/user/calendar/event123@example.com.ics');
|
|
186
|
-
t.truthy(responseObj['D:status']);
|
|
187
|
-
t.true(responseObj['D:status'].includes('404'));
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('event-response returns 200 status for non-deleted events', async (t) => {
|
|
191
|
-
const options = createMockOptions();
|
|
192
|
-
const eventResponse = eventResponseFactory(options);
|
|
193
|
-
const ctx = createMockCtx('/cal/user/calendar');
|
|
194
|
-
const calendar = createMockCalendar();
|
|
195
|
-
|
|
196
|
-
const events = [
|
|
197
|
-
{
|
|
198
|
-
eventId: 'active-event',
|
|
199
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
200
|
-
}
|
|
201
|
-
];
|
|
202
|
-
|
|
203
|
-
const children = [];
|
|
204
|
-
|
|
205
|
-
const result = await eventResponse(ctx, events, calendar, children);
|
|
206
|
-
|
|
207
|
-
t.truthy(result.responses);
|
|
208
|
-
t.is(result.responses.length, 1);
|
|
209
|
-
|
|
210
|
-
const responseObj = result.responses[0];
|
|
211
|
-
// Non-deleted events should have propstat, not status
|
|
212
|
-
t.truthy(responseObj['D:propstat']);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test('event-response handles multiple events correctly', async (t) => {
|
|
216
|
-
const options = createMockOptions();
|
|
217
|
-
const eventResponse = eventResponseFactory(options);
|
|
218
|
-
const ctx = createMockCtx('/cal/user/calendar');
|
|
219
|
-
const calendar = createMockCalendar();
|
|
220
|
-
|
|
221
|
-
const events = [
|
|
222
|
-
{
|
|
223
|
-
eventId: 'event1',
|
|
224
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
eventId: 'event2',
|
|
228
|
-
href: '/cal/user/calendar/custom-path.ics',
|
|
229
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
eventId: 'event3',
|
|
233
|
-
deleted_at: new Date(),
|
|
234
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
235
|
-
}
|
|
236
|
-
];
|
|
237
|
-
|
|
238
|
-
const children = [];
|
|
239
|
-
|
|
240
|
-
const result = await eventResponse(ctx, events, calendar, children);
|
|
241
|
-
|
|
242
|
-
t.truthy(result.responses);
|
|
243
|
-
t.is(result.responses.length, 3);
|
|
244
|
-
|
|
245
|
-
// Event 1: uses eventId
|
|
246
|
-
t.is(result.responses[0]['D:href'], '/cal/user/calendar/event1.ics');
|
|
247
|
-
|
|
248
|
-
// Event 2: uses href
|
|
249
|
-
t.is(result.responses[1]['D:href'], '/cal/user/calendar/custom-path.ics');
|
|
250
|
-
|
|
251
|
-
// Event 3: deleted, uses eventId (no href) - fallback URL construction
|
|
252
|
-
t.is(result.responses[2]['D:href'], '/cal/user/calendar/event3.ics');
|
|
253
|
-
t.true(result.responses[2]['D:status'].includes('404'));
|
|
254
|
-
});
|
|
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
|
-
|
|
304
|
-
test('event-response handles email-like eventId with @ symbol', async (t) => {
|
|
305
|
-
const options = createMockOptions();
|
|
306
|
-
const eventResponse = eventResponseFactory(options);
|
|
307
|
-
const ctx = createMockCtx('/cal/user/calendar');
|
|
308
|
-
const calendar = createMockCalendar();
|
|
309
|
-
|
|
310
|
-
// Event with email-like eventId (contains @)
|
|
311
|
-
const events = [
|
|
312
|
-
{
|
|
313
|
-
eventId: 'meeting@company.com',
|
|
314
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
315
|
-
}
|
|
316
|
-
];
|
|
317
|
-
|
|
318
|
-
const children = [];
|
|
319
|
-
|
|
320
|
-
const result = await eventResponse(ctx, events, calendar, children);
|
|
321
|
-
|
|
322
|
-
t.truthy(result.responses);
|
|
323
|
-
t.is(result.responses.length, 1);
|
|
324
|
-
|
|
325
|
-
// Should preserve the @ in the URL
|
|
326
|
-
t.is(
|
|
327
|
-
result.responses[0]['D:href'],
|
|
328
|
-
'/cal/user/calendar/meeting@company.com.ics'
|
|
329
|
-
);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
test('event-response handles special characters in eventId', async (t) => {
|
|
333
|
-
const options = createMockOptions();
|
|
334
|
-
const eventResponse = eventResponseFactory(options);
|
|
335
|
-
const ctx = createMockCtx('/cal/user/calendar');
|
|
336
|
-
const calendar = createMockCalendar();
|
|
337
|
-
|
|
338
|
-
// Event with special characters in eventId
|
|
339
|
-
const events = [
|
|
340
|
-
{
|
|
341
|
-
eventId: 'event-with-special_chars.123',
|
|
342
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
343
|
-
}
|
|
344
|
-
];
|
|
345
|
-
|
|
346
|
-
const children = [];
|
|
347
|
-
|
|
348
|
-
const result = await eventResponse(ctx, events, calendar, children);
|
|
349
|
-
|
|
350
|
-
t.truthy(result.responses);
|
|
351
|
-
t.is(result.responses.length, 1);
|
|
352
|
-
|
|
353
|
-
t.is(
|
|
354
|
-
result.responses[0]['D:href'],
|
|
355
|
-
'/cal/user/calendar/event-with-special_chars.123.ics'
|
|
356
|
-
);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
test('backwards compatibility: events without href use eventId', async (t) => {
|
|
360
|
-
const options = createMockOptions();
|
|
361
|
-
const eventResponse = eventResponseFactory(options);
|
|
362
|
-
const ctx = createMockCtx('/cal/user/calendar');
|
|
363
|
-
const calendar = createMockCalendar();
|
|
364
|
-
|
|
365
|
-
// Simulate existing event without href field (backwards compatibility)
|
|
366
|
-
const events = [
|
|
367
|
-
{
|
|
368
|
-
eventId: 'legacy-event-id',
|
|
369
|
-
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
370
|
-
// No href field - simulates existing events before this fix
|
|
371
|
-
}
|
|
372
|
-
];
|
|
373
|
-
|
|
374
|
-
const children = [];
|
|
375
|
-
|
|
376
|
-
const result = await eventResponse(ctx, events, calendar, children);
|
|
377
|
-
|
|
378
|
-
t.truthy(result.responses);
|
|
379
|
-
t.is(result.responses.length, 1);
|
|
380
|
-
|
|
381
|
-
// Should fall back to constructing URL from eventId
|
|
382
|
-
t.is(result.responses[0]['D:href'], '/cal/user/calendar/legacy-event-id.ics');
|
|
383
|
-
});
|