import { describe, expect, it } from "vitest"; import { createDavFixture, davRequest } from "@tests/worker/helpers/dav"; import { fetchWorker } from "@tests/worker/helpers/http"; import { lock, name } from "./locks-helpers"; describe("WebDAV lock basics", () => { it("denies writes without the active lock token and allows writes with it", async () => { const fixture = await createDavFixture(); const file = `${name("locked")}.txt`; expect(await fetchWorker(davRequest(fixture, `/files/${file}`, { method: "PUT", body: "first" }))).toHaveProperty( "status", 201, ); const token = await lock(fixture, `/files/${file}`); const denied = await fetchWorker(davRequest(fixture, `/files/${file}`, { method: "PUT", body: "blocked" })); expect(denied.status).toBe(423); const allowed = await fetchWorker( davRequest(fixture, `/files/${file}`, { method: "PUT", headers: { if: ` (${token})` }, body: "second", }), ); expect(allowed.status).toBe(204); const unlock = await fetchWorker( davRequest(fixture, `/files/${file}`, { method: "UNLOCK", headers: { "lock-token": token } }), ); expect(unlock.status).toBe(204); }); it("applies Depth infinity collection locks to descendants and supports refresh", async () => { const fixture = await createDavFixture(); const directory = name("locked-dir"); expect(await fetchWorker(davRequest(fixture, `/files/${directory}/`, { method: "MKCOL" }))).toHaveProperty( "status", 201, ); const token = await lock(fixture, `/files/${directory}/`, "infinity"); const denied = await fetchWorker( davRequest(fixture, `/files/${directory}/child.txt`, { method: "PUT", body: "x" }), ); expect(denied.status).toBe(423); const refresh = await fetchWorker( davRequest(fixture, `/files/${directory}/`, { method: "LOCK", headers: { if: `(${token})`, timeout: "Second-120" }, }), ); expect(refresh.status).toBe(200); const allowed = await fetchWorker( davRequest(fixture, `/files/${directory}/child.txt`, { method: "PUT", headers: { if: ` (${token})` }, body: "x", }), ); expect(allowed.status).toBe(201); }); it("creates a readable zero-byte resource for LOCK on an unmapped file URL", async () => { const fixture = await createDavFixture(); const file = `${name("unmapped-lock")}.txt`; const response = await fetchWorker( davRequest(fixture, `/files/${file}`, { method: "LOCK", headers: { depth: "0", timeout: "Second-600", "content-type": "application/xml" }, body: ``, }), ); expect(response.status).toBe(201); const token = response.headers.get("lock-token"); expect(token).toMatch(/^]+>$/); const get = await fetchWorker(davRequest(fixture, `/files/${file}`)); expect(get.status).toBe(200); expect(get.headers.get("content-length")).toBe("0"); await expect(get.arrayBuffer()).resolves.toHaveProperty("byteLength", 0); const head = await fetchWorker(davRequest(fixture, `/files/${file}`, { method: "HEAD" })); expect(head.status).toBe(200); expect(head.headers.get("content-length")).toBe("0"); const put = await fetchWorker( davRequest(fixture, `/files/${file}`, { method: "PUT", headers: { if: `(${token})` }, body: "replaced", }), ); expect(put.status).toBe(204); await expect((await fetchWorker(davRequest(fixture, `/files/${file}`))).text()).resolves.toBe("replaced"); }); it("includes inherited Depth infinity locks in lockdiscovery", async () => { const fixture = await createDavFixture(); const directory = name("lockdiscovery"); expect(await fetchWorker(davRequest(fixture, `/files/${directory}/`, { method: "MKCOL" }))).toHaveProperty( "status", 201, ); expect( await fetchWorker(davRequest(fixture, `/files/${directory}/child.txt`, { method: "PUT", body: "x" })), ).toHaveProperty("status", 201); const token = await lock(fixture, `/files/${directory}/`, "infinity"); const propfind = await fetchWorker( davRequest(fixture, `/files/${directory}/child.txt`, { method: "PROPFIND", headers: { depth: "0", "content-type": "application/xml" }, body: ``, }), ); expect(propfind.status).toBe(207); expect(await propfind.text()).toContain(token.slice(1, -1)); }); });