File
Blob: tests/worker/protocol/caldav/helpers.ts
| 1 | import { expect } from "vitest"; |
| 2 | |
| 3 | import { createDavFixture, davRequest } from "@tests/worker/helpers/dav"; |
| 4 | import { fetchWorker } from "@tests/worker/helpers/http"; |
| 5 | |
| 6 | type DavFixture = Awaited<ReturnType<typeof createDavFixture>>; |
| 7 | |
| 8 | export function uid(): string { |
| 9 | return `${crypto.randomUUID()}@example.test`; |
| 10 | } |
| 11 | |
| 12 | export function event(input: { |
| 13 | uid: string; |
| 14 | summary: string; |
| 15 | start?: string; |
| 16 | end?: string; |
| 17 | rrule?: string; |
| 18 | categories?: string[]; |
| 19 | }): string { |
| 20 | return [ |
| 21 | "BEGIN:VCALENDAR", |
| 22 | "VERSION:2.0", |
| 23 | "PRODID:-//dab//tests//EN", |
| 24 | "BEGIN:VEVENT", |
| 25 | `UID:${input.uid}`, |
| 26 | "DTSTAMP:20260101T000000Z", |
| 27 | `SUMMARY:${input.summary}`, |
| 28 | `DTSTART:${input.start ?? "20260521T120000Z"}`, |
| 29 | `DTEND:${input.end ?? "20260521T130000Z"}`, |
| 30 | input.categories ? `CATEGORIES:${input.categories.join(",")}` : null, |
| 31 | input.rrule ? `RRULE:${input.rrule}` : null, |
| 32 | "END:VEVENT", |
| 33 | "END:VCALENDAR", |
| 34 | "", |
| 35 | ] |
| 36 | .filter((line): line is string => line !== null) |
| 37 | .join("\r\n"); |
| 38 | } |
| 39 | |
| 40 | export function propfindBody(props: string): string { |
| 41 | return `<?xml version="1.0"?><D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/"><D:prop>${props}</D:prop></D:propfind>`; |
| 42 | } |
| 43 | |
| 44 | export function mkcolCalendarBody(displayName: string, timezoneIcal?: string): string { |
| 45 | return [ |
| 46 | '<?xml version="1.0"?>', |
| 47 | '<D:mkcol xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">', |
| 48 | "<D:set><D:prop><D:resourcetype><D:collection/><C:calendar/></D:resourcetype>", |
| 49 | `<D:displayname>${displayName}</D:displayname>`, |
| 50 | timezoneIcal ? `<C:calendar-timezone>${timezoneIcal}</C:calendar-timezone>` : null, |
| 51 | "</D:prop></D:set></D:mkcol>", |
| 52 | ] |
| 53 | .filter((line): line is string => line !== null) |
| 54 | .join(""); |
| 55 | } |
| 56 | |
| 57 | export function reportBody(name: string, body: string): string { |
| 58 | return name === "sync-collection" |
| 59 | ? `<?xml version="1.0"?><D:sync-collection xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">${body}</D:sync-collection>` |
| 60 | : `<?xml version="1.0"?><C:${name} xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">${body}</C:${name}>`; |
| 61 | } |
| 62 | |
| 63 | export function syncCollectionBody(prevToken: string, props = "<D:getetag/>"): string { |
| 64 | return reportBody( |
| 65 | "sync-collection", |
| 66 | `<D:sync-token>${prevToken}</D:sync-token><D:sync-level>1</D:sync-level><D:prop>${props}</D:prop>`, |
| 67 | ); |
| 68 | } |
| 69 | |
| 70 | export async function syncToken(fixture: DavFixture): Promise<string> { |
| 71 | const response = await fetchWorker( |
| 72 | davRequest(fixture, "/calendars/default/", { |
| 73 | method: "REPORT", |
| 74 | headers: { "content-type": "application/xml" }, |
| 75 | body: syncCollectionBody(""), |
| 76 | }), |
| 77 | ); |
| 78 | expect(response.status).toBe(207); |
| 79 | const token = /<D:sync-token>([^<]+)<\/D:sync-token>/.exec(await response.text())?.[1]; |
| 80 | if (!token) throw new Error("Missing sync-token in baseline sync-collection response"); |
| 81 | return token; |
| 82 | } |
| 83 | |
| 84 | export async function propfindSyncToken(fixture: DavFixture, href = "/calendars/default/") { |
| 85 | const response = await fetchWorker( |
| 86 | davRequest(fixture, href, { |
| 87 | method: "PROPFIND", |
| 88 | headers: { depth: "0", "content-type": "application/xml" }, |
| 89 | body: propfindBody("<D:sync-token/>"), |
| 90 | }), |
| 91 | ); |
| 92 | expect(response.status).toBe(207); |
| 93 | const token = /<D:sync-token>([^<]+)<\/D:sync-token>/.exec(await response.text())?.[1]; |
| 94 | if (!token) throw new Error("Missing sync-token in PROPFIND response"); |
| 95 | return token; |
| 96 | } |