Skip to content
File

Blob: tests/worker/protocol/carddav/addressbook-collections.workers.test.ts

typescript90 lines
1import { env } from "cloudflare:workers";
2import { describe, expect, it } from "vitest";
3 
4import { createDavFixture, davRequest } from "@tests/worker/helpers/dav";
5import { fetchWorker } from "@tests/worker/helpers/http";
6import { propfindBody, storageIdFor } from "./helpers";
7 
8describe("CardDAV address book collections", () => {
9 it("advertises sync:0 getctag for newly created address books", async () => {
10 const fixture = await createDavFixture(["carddav.full"]);
11 const bookName = `fresh-ctag-${crypto.randomUUID()}`;
12 const storageId = await storageIdFor(fixture.subjectId);
13 const created = await env.CARD_DAV.getByName(storageId).createAddressbook({
14 subjectId: fixture.subjectId,
15 name: bookName,
16 displayName: "Fresh Ctag",
17 nowMs: Date.now(),
18 });
19 expect(created.ok).toBe(true);
20 
21 const propfind = await fetchWorker(
22 davRequest(fixture, `/addressbooks/${bookName}/`, {
23 method: "PROPFIND",
24 headers: { depth: "0", "content-type": "application/xml" },
25 body: propfindBody("<CS:getctag/>"),
26 }),
27 );
28 expect(propfind.status).toBe(207);
29 expect(await propfind.text()).toContain('<CS:getctag xmlns:CS="http://calendarserver.org/ns/">sync:0</CS:getctag>');
30 });
31 
32 it("stores address book dead properties with PROPPATCH", async () => {
33 const fixture = await createDavFixture(["carddav.full"]);
34 const patch = await fetchWorker(
35 davRequest(fixture, "/addressbooks/default/", {
36 method: "PROPPATCH",
37 headers: { "content-type": "application/xml" },
38 body: [
39 '<?xml version="1.0"?>',
40 '<D:propertyupdate xmlns:D="DAV:" xmlns:X="urn:example:test">',
41 "<D:set><D:prop><X:label>Friends</X:label></D:prop></D:set>",
42 "</D:propertyupdate>",
43 ].join(""),
44 }),
45 );
46 expect(patch.status).toBe(207);
47 
48 const propfind = await fetchWorker(
49 davRequest(fixture, "/addressbooks/default/", {
50 method: "PROPFIND",
51 headers: { depth: "0", "content-type": "application/xml" },
52 body: propfindBody('<X:label xmlns:X="urn:example:test"/>'),
53 }),
54 );
55 expect(propfind.status).toBe(207);
56 await expect(propfind.text()).resolves.toContain("Friends");
57 });
58 
59 it("returns per-property PROPPATCH failures atomically for protected CardDAV properties", async () => {
60 const fixture = await createDavFixture(["carddav.full"]);
61 const patch = await fetchWorker(
62 davRequest(fixture, "/addressbooks/default/", {
63 method: "PROPPATCH",
64 headers: { "content-type": "application/xml" },
65 body: [
66 '<?xml version="1.0"?>',
67 '<D:propertyupdate xmlns:D="DAV:" xmlns:X="urn:test">',
68 "<D:set><D:prop><X:label>Friends</X:label></D:prop></D:set>",
69 "<D:set><D:prop><D:getetag>bad</D:getetag></D:prop></D:set>",
70 "</D:propertyupdate>",
71 ].join(""),
72 }),
73 );
74 expect(patch.status).toBe(207);
75 const patchXml = await patch.text();
76 expect(patchXml).toContain("HTTP/1.1 403 Forbidden");
77 expect(patchXml).toContain("HTTP/1.1 424 Failed Dependency");
78 
79 const propfind = await fetchWorker(
80 davRequest(fixture, "/addressbooks/default/", {
81 method: "PROPFIND",
82 headers: { depth: "0", "content-type": "application/xml" },
83 body: propfindBody('<X:label xmlns:X="urn:test"/>'),
84 }),
85 );
86 expect(propfind.status).toBe(207);
87 expect(await propfind.text()).toContain("HTTP/1.1 404 Not Found");
88 });
89});