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);
});
});