import { describe, expect, it } from "vitest"; import { createDavFixture, davRequest } from "@tests/worker/helpers/dav"; import { controlRequest, fetchWorker } from "@tests/worker/helpers/http"; function propfindBody(props: string): string { return `${props}`; } function allpropIncludeBody(props: string): string { return `${props}`; } describe("DAV discovery", () => { it("serves files-only principal discovery without exposing CalDAV or CardDAV homes", async () => { const fixture = await createDavFixture(["files.readonly"]); const response = await fetchWorker( davRequest(fixture, "/", { method: "PROPFIND", headers: { depth: "0", "content-type": "application/xml" }, body: propfindBody( "", ), }), ); expect(response.status).toBe(207); const xml = await response.text(); expect(xml).toContain("/principals/me/"); expect(xml).toContain(`/principals/${fixture.hostLabel}/`); expect(xml).toContain(""); expect(xml).not.toContain("/calendars/"); expect(xml).not.toContain("/addressbooks/"); expect(xml).not.toContain(fixture.subjectId); }); it("advertises CalDAV homes and read-only privileges to a read-only CalDAV PAT", async () => { const fixture = await createDavFixture(["caldav.readonly"]); const root = await fetchWorker( davRequest(fixture, "/principals/me/", { method: "PROPFIND", headers: { depth: "0", "content-type": "application/xml" }, body: propfindBody( "", ), }), ); expect(root.status).toBe(207); const rootXml = await root.text(); expect(rootXml).toContain("/calendars/"); expect(rootXml).toContain("calendar-user-address-set"); expect(rootXml).toContain("read-current-user-privilege-set"); expect(rootXml).not.toContain("/addressbooks/"); const calendarHome = await fetchWorker( davRequest(fixture, "/calendars/", { method: "PROPFIND", headers: { depth: "1", "content-type": "application/xml" }, body: propfindBody(""), }), ); expect(calendarHome.status).toBe(207); const calendarXml = await calendarHome.text(); expect(calendarXml).toContain("/calendars/default/"); expect(calendarXml).toContain(""); expect(calendarXml).toContain("read-current-user-privilege-set"); expect(calendarXml).toContain( "", ); }); it("advertises CardDAV homes and write privileges to a full CardDAV PAT", async () => { const fixture = await createDavFixture(["carddav.full"]); const principal = await fetchWorker( davRequest(fixture, `/principals/${fixture.hostLabel}/`, { method: "PROPFIND", headers: { depth: "0", "content-type": "application/xml" }, body: propfindBody(""), }), ); expect(principal.status).toBe(207); const principalXml = await principal.text(); expect(principalXml).toContain(`/principals/${fixture.hostLabel}/`); expect(principalXml).toContain("/addressbooks/"); const addressbook = await fetchWorker( davRequest(fixture, "/addressbooks/default/", { method: "PROPFIND", headers: { depth: "0", "content-type": "application/xml" }, body: propfindBody(""), }), ); expect(addressbook.status).toBe(207); const addressbookXml = await addressbook.text(); expect(addressbookXml).toContain(""); expect(addressbookXml).toContain(""); }); it("honors allprop include properties on discovery resources", async () => { const fixture = await createDavFixture(["files.readonly"]); const response = await fetchWorker( davRequest(fixture, "/", { method: "PROPFIND", headers: { depth: "0", "content-type": "application/xml" }, body: allpropIncludeBody(""), }), ); expect(response.status).toBe(207); const xml = await response.text(); expect(xml).toContain("addressbook-home-set"); expect(xml).not.toContain("/addressbooks/"); }); it("allows write-only scopes to discover the matching protocol home", async () => { const fixture = await createDavFixture(["dav:caldav:write"]); const principal = await fetchWorker( davRequest(fixture, "/principals/me/", { method: "PROPFIND", headers: { depth: "0", "content-type": "application/xml" }, body: propfindBody(""), }), ); expect(principal.status).toBe(207); expect(await principal.text()).toContain("/calendars/"); const calendarHome = await fetchWorker( davRequest(fixture, "/calendars/", { method: "PROPFIND", headers: { depth: "0", "content-type": "application/xml" }, body: propfindBody(""), }), ); expect(calendarHome.status).toBe(207); const calendarXml = await calendarHome.text(); expect(calendarXml).toContain(""); expect(calendarXml).toContain(""); }); it("fails closed for unknown principal labels and control-plane DAV paths", async () => { const fixture = await createDavFixture(["dav.full"]); const unknownPrincipal = await fetchWorker( davRequest(fixture, "/principals/not-the-label/", { method: "PROPFIND", headers: { "content-type": "application/xml" }, body: propfindBody(""), }), ); expect(unknownPrincipal.status).toBe(404); const controlPlaneDav = await fetchWorker( controlRequest("/principals/me/", { method: "PROPFIND", headers: { authorization: fixture.authHeader, "content-type": "application/xml" }, body: propfindBody(""), }), ); expect(controlPlaneDav.status).toBe(404); }); it("resolves subject hosts before well-known redirects", async () => { const fixture = await createDavFixture(["dav.full"]); const known = await fetchWorker(davRequest(fixture, "/.well-known/caldav", { redirect: "manual" })); expect(known.status).toBe(302); const unknown = await fetchWorker( new Request("https://river-copper-lantern-velvet-maple.dav.example.com/.well-known/caldav"), ); expect(unknown.status).toBe(404); }); it("authenticates subject-host OPTIONS and enforces subject ownership", async () => { const fixture = await createDavFixture(["dav.full"]); const unauthenticated = await fetchWorker( new Request(`https://${fixture.hostLabel}.dav.example.com/files/`, { method: "OPTIONS" }), ); expect(unauthenticated.status).toBe(401); const unknownSubject = await fetchWorker( new Request("https://river-copper-lantern-velvet-maple.dav.example.com/files/", { method: "OPTIONS", headers: { authorization: fixture.authHeader }, }), ); expect(unknownSubject.status).toBe(404); const authenticated = await fetchWorker(davRequest(fixture, "/files/", { method: "OPTIONS" })); expect(authenticated.status).toBe(204); expect(authenticated.headers.get("allow")).toContain("PROPFIND"); }); it("rejects insecure production DAV requests before Basic auth challenge", async () => { const fixture = await createDavFixture(["dav.full"]); const response = await fetchWorker(new Request(`http://${fixture.hostLabel}.dav.example.com/files/`)); expect(response.status).toBe(403); expect(response.headers.has("www-authenticate")).toBe(false); }); });