File
Blob: tests/worker/protocol/dav/html-listings.workers.test.ts
| 1 | import { describe, expect, it } from "vitest"; |
| 2 | |
| 3 | import { createDavFixture, davRequest } from "@tests/worker/helpers/dav"; |
| 4 | import { fetchWorker } from "@tests/worker/helpers/http"; |
| 5 | import { event, uid as calendarUid } from "@tests/worker/protocol/caldav/helpers"; |
| 6 | import { uid as addressUid, vcard } from "@tests/worker/protocol/carddav/helpers"; |
| 7 | |
| 8 | function encoded(segment: string): string { |
| 9 | return encodeURIComponent(segment); |
| 10 | } |
| 11 | |
| 12 | describe("DAV browser HTML listings", () => { |
| 13 | it("renders scoped root discovery listings", async () => { |
| 14 | const fixture = await createDavFixture(["files.readonly"]); |
| 15 | |
| 16 | const response = await fetchWorker(davRequest(fixture, "/")); |
| 17 | expect(response.status).toBe(200); |
| 18 | expect(response.headers.get("content-type")).toContain("text/html"); |
| 19 | expect(response.headers.get("cache-control")).toBe("no-store"); |
| 20 | expect(response.headers.get("content-security-policy")).toContain("default-src 'none'"); |
| 21 | |
| 22 | const html = await response.text(); |
| 23 | expect(html).toContain("WebDAV file storage"); |
| 24 | expect(html).toContain("/files/"); |
| 25 | expect(html).not.toContain("/calendars/"); |
| 26 | expect(html).not.toContain("/addressbooks/"); |
| 27 | |
| 28 | expect(await fetchWorker(davRequest(fixture, "/calendars/"))).toHaveProperty("status", 403); |
| 29 | expect(await fetchWorker(davRequest(fixture, "/addressbooks/"))).toHaveProperty("status", 403); |
| 30 | |
| 31 | const head = await fetchWorker(davRequest(fixture, "/", { method: "HEAD" })); |
| 32 | expect(head.status).toBe(200); |
| 33 | expect(head.headers.get("content-type")).toContain("text/html"); |
| 34 | await expect(head.text()).resolves.toBe(""); |
| 35 | }); |
| 36 | |
| 37 | it("renders file collection listings and keeps file GET unchanged", async () => { |
| 38 | const fixture = await createDavFixture(["files.full"]); |
| 39 | const directory = `browser-${crypto.randomUUID()}`; |
| 40 | const fileName = `budget <Q2> & notes-${crypto.randomUUID()}.txt`; |
| 41 | const fileHref = `/files/${directory}/${encoded(fileName)}`; |
| 42 | |
| 43 | expect(await fetchWorker(davRequest(fixture, `/files/${directory}/`, { method: "MKCOL" }))).toHaveProperty( |
| 44 | "status", |
| 45 | 201, |
| 46 | ); |
| 47 | expect( |
| 48 | await fetchWorker( |
| 49 | davRequest(fixture, fileHref, { |
| 50 | method: "PUT", |
| 51 | headers: { "content-type": "text/plain" }, |
| 52 | body: "listing body", |
| 53 | }), |
| 54 | ), |
| 55 | ).toHaveProperty("status", 201); |
| 56 | |
| 57 | const listing = await fetchWorker(davRequest(fixture, `/files/${directory}/`)); |
| 58 | expect(listing.status).toBe(200); |
| 59 | expect(listing.headers.get("content-type")).toContain("text/html"); |
| 60 | const html = await listing.text(); |
| 61 | expect(html).toContain("Parent directory"); |
| 62 | expect(html).toContain("budget <Q2> & notes"); |
| 63 | expect(html).not.toContain(fileName); |
| 64 | |
| 65 | const file = await fetchWorker(davRequest(fixture, fileHref)); |
| 66 | expect(file.status).toBe(200); |
| 67 | expect(file.headers.get("content-type")).toBe("text/plain"); |
| 68 | await expect(file.text()).resolves.toBe("listing body"); |
| 69 | }); |
| 70 | |
| 71 | it("renders calendar listings and keeps calendar object GET unchanged", async () => { |
| 72 | const fixture = await createDavFixture(["caldav.full"]); |
| 73 | const href = "/calendars/default/listing.ics"; |
| 74 | const body = event({ uid: calendarUid(), summary: "Planning <Q2> & Sync" }); |
| 75 | |
| 76 | expect( |
| 77 | await fetchWorker( |
| 78 | davRequest(fixture, href, { |
| 79 | method: "PUT", |
| 80 | headers: { "content-type": "text/calendar; charset=utf-8" }, |
| 81 | body, |
| 82 | }), |
| 83 | ), |
| 84 | ).toHaveProperty("status", 201); |
| 85 | |
| 86 | const home = await fetchWorker(davRequest(fixture, "/calendars/")); |
| 87 | expect(home.status).toBe(200); |
| 88 | expect(await home.text()).toContain("/calendars/default/"); |
| 89 | |
| 90 | const collection = await fetchWorker(davRequest(fixture, "/calendars/default/")); |
| 91 | expect(collection.status).toBe(200); |
| 92 | const html = await collection.text(); |
| 93 | expect(html).toContain("listing.ics"); |
| 94 | expect(html).toContain("VEVENT - Planning <Q2> & Sync"); |
| 95 | |
| 96 | const object = await fetchWorker(davRequest(fixture, href)); |
| 97 | expect(object.status).toBe(200); |
| 98 | expect(object.headers.get("content-type")).toBe("text/calendar; charset=utf-8"); |
| 99 | await expect(object.text()).resolves.toBe(body); |
| 100 | }); |
| 101 | |
| 102 | it("renders address book listings and keeps vCard object GET unchanged", async () => { |
| 103 | const fixture = await createDavFixture(["carddav.full"]); |
| 104 | const href = "/addressbooks/default/listing.vcf"; |
| 105 | const body = vcard({ uid: addressUid(), fn: "Alice <Ops> & Co", email: "alice@example.test" }); |
| 106 | |
| 107 | expect( |
| 108 | await fetchWorker( |
| 109 | davRequest(fixture, href, { |
| 110 | method: "PUT", |
| 111 | headers: { "content-type": "text/vcard; charset=utf-8" }, |
| 112 | body, |
| 113 | }), |
| 114 | ), |
| 115 | ).toHaveProperty("status", 201); |
| 116 | |
| 117 | const home = await fetchWorker(davRequest(fixture, "/addressbooks/")); |
| 118 | expect(home.status).toBe(200); |
| 119 | expect(await home.text()).toContain("/addressbooks/default/"); |
| 120 | |
| 121 | const collection = await fetchWorker(davRequest(fixture, "/addressbooks/default/")); |
| 122 | expect(collection.status).toBe(200); |
| 123 | const html = await collection.text(); |
| 124 | expect(html).toContain("listing.vcf"); |
| 125 | expect(html).toContain("Alice <Ops> & Co - alice@example.test"); |
| 126 | |
| 127 | const object = await fetchWorker(davRequest(fixture, href)); |
| 128 | expect(object.status).toBe(200); |
| 129 | expect(object.headers.get("content-type")).toBe("text/vcard; charset=utf-8"); |
| 130 | await expect(object.text()).resolves.toBe(body); |
| 131 | }); |
| 132 | }); |