import { describe, expect, it } from "vitest";
import { createDavFixture, davRequest } from "@tests/worker/helpers/dav";
import { fetchWorker } from "@tests/worker/helpers/http";
import { withEnvBinding } from "@tests/worker/helpers/env";
import { reportBody, uid, vcard } from "./helpers";
describe("CardDAV report limits and origins", () => {
it("applies the configured REPORT result limit to addressbook-query and addressbook-multiget", async () => {
const fixture = await createDavFixture(["carddav.full"]);
const firstName = `limited-a-${crypto.randomUUID()}.vcf`;
const secondName = `limited-b-${crypto.randomUUID()}.vcf`;
expect(
await fetchWorker(
davRequest(fixture, `/addressbooks/default/${firstName}`, {
method: "PUT",
body: vcard({ uid: uid(), fn: "Limited A" }),
}),
),
).toMatchObject({ status: 201 });
expect(
await fetchWorker(
davRequest(fixture, `/addressbooks/default/${secondName}`, {
method: "PUT",
body: vcard({ uid: uid(), fn: "Limited B" }),
}),
),
).toMatchObject({ status: 201 });
await withEnvBinding("MAX_REPORT_RESULTS", "1", async () => {
// RFC 6352 8.3: when an addressbook-query result set exceeds the configured
// limit, return a DAV:number-of-matches-within-limits precondition error.
const query = await fetchWorker(
davRequest(fixture, "/addressbooks/default/", {
method: "REPORT",
headers: { "content-type": "application/xml" },
body: reportBody("addressbook-query", ""),
}),
);
expect(query.status).toBe(403);
await expect(query.text()).resolves.toContain("number-of-matches-within-limits");
const multiget = await fetchWorker(
davRequest(fixture, "/addressbooks/default/", {
method: "REPORT",
headers: { "content-type": "application/xml" },
body: reportBody(
"addressbook-multiget",
`/addressbooks/default/${firstName}/addressbooks/default/${secondName}`,
),
}),
);
expect(multiget.status).toBe(403);
});
});
it("rejects unsupported addressbook-query text-match options", async () => {
const fixture = await createDavFixture(["carddav.full"]);
const unsupportedCollation = await fetchWorker(
davRequest(fixture, "/addressbooks/default/", {
method: "REPORT",
headers: { "content-type": "application/xml" },
body: reportBody(
"addressbook-query",
'alice',
),
}),
);
expect(unsupportedCollation.status).toBe(400);
await expect(unsupportedCollation.text()).resolves.toContain("Unsupported CardDAV text-match collation");
const tooLarge = await fetchWorker(
davRequest(fixture, "/addressbooks/default/", {
method: "REPORT",
headers: { "content-type": "application/xml" },
body: reportBody(
"addressbook-query",
`${"x".repeat(1025)}`,
),
}),
);
expect(tooLarge.status).toBe(400);
await expect(tooLarge.text()).resolves.toContain("CardDAV text-match is limited");
});
it("rejects addressbook-multiget absolute hrefs outside the subject origin", async () => {
const fixture = await createDavFixture(["carddav.full"]);
const name = `absolute-origin-${crypto.randomUUID()}.vcf`;
const contactUid = uid();
expect(
await fetchWorker(
davRequest(fixture, `/addressbooks/default/${name}`, {
method: "PUT",
body: vcard({ uid: contactUid, fn: "Absolute Origin" }),
}),
),
).toMatchObject({ status: 201 });
const multiget = await fetchWorker(
davRequest(fixture, "/addressbooks/default/", {
method: "REPORT",
headers: { "content-type": "application/xml" },
body: reportBody(
"addressbook-multiget",
`https://other.example.test/addressbooks/default/${name}`,
),
}),
);
expect(multiget.status).toBe(207);
const xml = await multiget.text();
expect(xml).toContain(`https://other.example.test/addressbooks/default/${name}`);
expect(xml).toContain("HTTP/1.1 404 Not Found");
expect(xml).not.toContain(contactUid);
});
});