File
Blob: tests/worker/protocol/webdav/files-operations.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 { name } from "./files-helpers"; |
| 6 | |
| 7 | describe("WebDAV file operations", () => { |
| 8 | it("copies, moves, and deletes metadata while preserving blob bytes", async () => { |
| 9 | const fixture = await createDavFixture(); |
| 10 | const directory = name("copy-move"); |
| 11 | expect(await fetchWorker(davRequest(fixture, `/files/${directory}/`, { method: "MKCOL" }))).toMatchObject({ |
| 12 | status: 201, |
| 13 | }); |
| 14 | expect( |
| 15 | await fetchWorker(davRequest(fixture, `/files/${directory}/a.txt`, { method: "PUT", body: "copy me" })), |
| 16 | ).toHaveProperty("status", 201); |
| 17 | |
| 18 | const copy = await fetchWorker( |
| 19 | davRequest(fixture, `/files/${directory}/a.txt`, { |
| 20 | method: "COPY", |
| 21 | headers: { destination: `https://${fixture.hostLabel}.dav.example.com/files/${directory}/b.txt` }, |
| 22 | }), |
| 23 | ); |
| 24 | expect(copy.status).toBe(201); |
| 25 | |
| 26 | const move = await fetchWorker( |
| 27 | davRequest(fixture, `/files/${directory}/b.txt`, { |
| 28 | method: "MOVE", |
| 29 | headers: { destination: `https://${fixture.hostLabel}.dav.example.com/files/${directory}/c.txt` }, |
| 30 | }), |
| 31 | ); |
| 32 | expect(move.status).toBe(201); |
| 33 | |
| 34 | const moved = await fetchWorker(davRequest(fixture, `/files/${directory}/c.txt`)); |
| 35 | expect(moved.status).toBe(200); |
| 36 | await expect(moved.text()).resolves.toBe("copy me"); |
| 37 | |
| 38 | const deleted = await fetchWorker(davRequest(fixture, `/files/${directory}/`, { method: "DELETE" })); |
| 39 | expect(deleted.status).toBe(204); |
| 40 | const missing = await fetchWorker(davRequest(fixture, `/files/${directory}/c.txt`)); |
| 41 | expect(missing.status).toBe(404); |
| 42 | }); |
| 43 | |
| 44 | it("rejects invalid DELETE, COPY, and MOVE Depth headers before mutation", async () => { |
| 45 | const fixture = await createDavFixture(); |
| 46 | const directory = name("invalid-depth"); |
| 47 | expect(await fetchWorker(davRequest(fixture, `/files/${directory}/`, { method: "MKCOL" }))).toHaveProperty( |
| 48 | "status", |
| 49 | 201, |
| 50 | ); |
| 51 | expect( |
| 52 | await fetchWorker(davRequest(fixture, `/files/${directory}/a.txt`, { method: "PUT", body: "depth" })), |
| 53 | ).toHaveProperty("status", 201); |
| 54 | |
| 55 | const invalidCopy = await fetchWorker( |
| 56 | davRequest(fixture, `/files/${directory}/a.txt`, { |
| 57 | method: "COPY", |
| 58 | headers: { |
| 59 | depth: "1", |
| 60 | destination: `https://${fixture.hostLabel}.dav.example.com/files/${directory}/copy.txt`, |
| 61 | }, |
| 62 | }), |
| 63 | ); |
| 64 | expect(invalidCopy.status).toBe(400); |
| 65 | await expect(invalidCopy.text()).resolves.toContain("COPY Depth must be 0 or infinity"); |
| 66 | expect(await fetchWorker(davRequest(fixture, `/files/${directory}/copy.txt`))).toHaveProperty("status", 404); |
| 67 | |
| 68 | const invalidMove = await fetchWorker( |
| 69 | davRequest(fixture, `/files/${directory}/a.txt`, { |
| 70 | method: "MOVE", |
| 71 | headers: { |
| 72 | depth: "0", |
| 73 | destination: `https://${fixture.hostLabel}.dav.example.com/files/${directory}/moved.txt`, |
| 74 | }, |
| 75 | }), |
| 76 | ); |
| 77 | expect(invalidMove.status).toBe(400); |
| 78 | await expect(invalidMove.text()).resolves.toContain("MOVE Depth must be infinity"); |
| 79 | expect(await fetchWorker(davRequest(fixture, `/files/${directory}/a.txt`))).toHaveProperty("status", 200); |
| 80 | expect(await fetchWorker(davRequest(fixture, `/files/${directory}/moved.txt`))).toHaveProperty("status", 404); |
| 81 | |
| 82 | const invalidDelete = await fetchWorker( |
| 83 | davRequest(fixture, `/files/${directory}/a.txt`, { method: "DELETE", headers: { depth: "0" } }), |
| 84 | ); |
| 85 | expect(invalidDelete.status).toBe(400); |
| 86 | await expect(invalidDelete.text()).resolves.toContain("DELETE Depth must be infinity"); |
| 87 | expect(await fetchWorker(davRequest(fixture, `/files/${directory}/a.txt`))).toHaveProperty("status", 200); |
| 88 | }); |
| 89 | |
| 90 | it("rejects file self-copy without changing body or dead properties", async () => { |
| 91 | const fixture = await createDavFixture(); |
| 92 | const file = `${name("self-copy")}.txt`; |
| 93 | const href = `/files/${file}`; |
| 94 | expect(await fetchWorker(davRequest(fixture, href, { method: "PUT", body: "before" }))).toHaveProperty( |
| 95 | "status", |
| 96 | 201, |
| 97 | ); |
| 98 | expect( |
| 99 | await fetchWorker( |
| 100 | davRequest(fixture, href, { |
| 101 | method: "PROPPATCH", |
| 102 | headers: { "content-type": "application/xml" }, |
| 103 | body: `<?xml version="1.0"?><D:propertyupdate xmlns:D="DAV:" xmlns:Z="urn:test"><D:set><D:prop><Z:color>blue</Z:color></D:prop></D:set></D:propertyupdate>`, |
| 104 | }), |
| 105 | ), |
| 106 | ).toHaveProperty("status", 207); |
| 107 | |
| 108 | const copied = await fetchWorker( |
| 109 | davRequest(fixture, href, { |
| 110 | method: "COPY", |
| 111 | headers: { destination: `https://${fixture.hostLabel}.dav.example.com${href}` }, |
| 112 | }), |
| 113 | ); |
| 114 | expect(copied.status).toBe(403); |
| 115 | |
| 116 | const get = await fetchWorker(davRequest(fixture, href)); |
| 117 | expect(get.status).toBe(200); |
| 118 | await expect(get.text()).resolves.toBe("before"); |
| 119 | const propfind = await fetchWorker( |
| 120 | davRequest(fixture, href, { |
| 121 | method: "PROPFIND", |
| 122 | headers: { depth: "0", "content-type": "application/xml" }, |
| 123 | body: `<?xml version="1.0"?><D:propfind xmlns:D="DAV:" xmlns:Z="urn:test"><D:prop><Z:color/></D:prop></D:propfind>`, |
| 124 | }), |
| 125 | ); |
| 126 | expect(propfind.status).toBe(207); |
| 127 | await expect(propfind.text()).resolves.toContain("blue"); |
| 128 | }); |
| 129 | }); |