Skip to content
File

Blob: tests/worker/protocol/webdav/locks-basic.workers.test.ts

typescript125 lines
1import { describe, expect, it } from "vitest";
2 
3import { createDavFixture, davRequest } from "@tests/worker/helpers/dav";
4import { fetchWorker } from "@tests/worker/helpers/http";
5import { lock, name } from "./locks-helpers";
6 
7describe("WebDAV lock basics", () => {
8 it("denies writes without the active lock token and allows writes with it", async () => {
9 const fixture = await createDavFixture();
10 const file = `${name("locked")}.txt`;
11 expect(await fetchWorker(davRequest(fixture, `/files/${file}`, { method: "PUT", body: "first" }))).toHaveProperty(
12 "status",
13 201,
14 );
15 const token = await lock(fixture, `/files/${file}`);
16 
17 const denied = await fetchWorker(davRequest(fixture, `/files/${file}`, { method: "PUT", body: "blocked" }));
18 expect(denied.status).toBe(423);
19 
20 const allowed = await fetchWorker(
21 davRequest(fixture, `/files/${file}`, {
22 method: "PUT",
23 headers: { if: `<https://${fixture.hostLabel}.dav.example.com/files/${file}> (${token})` },
24 body: "second",
25 }),
26 );
27 expect(allowed.status).toBe(204);
28 
29 const unlock = await fetchWorker(
30 davRequest(fixture, `/files/${file}`, { method: "UNLOCK", headers: { "lock-token": token } }),
31 );
32 expect(unlock.status).toBe(204);
33 });
34 
35 it("applies Depth infinity collection locks to descendants and supports refresh", async () => {
36 const fixture = await createDavFixture();
37 const directory = name("locked-dir");
38 expect(await fetchWorker(davRequest(fixture, `/files/${directory}/`, { method: "MKCOL" }))).toHaveProperty(
39 "status",
40 201,
41 );
42 const token = await lock(fixture, `/files/${directory}/`, "infinity");
43 
44 const denied = await fetchWorker(
45 davRequest(fixture, `/files/${directory}/child.txt`, { method: "PUT", body: "x" }),
46 );
47 expect(denied.status).toBe(423);
48 
49 const refresh = await fetchWorker(
50 davRequest(fixture, `/files/${directory}/`, {
51 method: "LOCK",
52 headers: { if: `(${token})`, timeout: "Second-120" },
53 }),
54 );
55 expect(refresh.status).toBe(200);
56 
57 const allowed = await fetchWorker(
58 davRequest(fixture, `/files/${directory}/child.txt`, {
59 method: "PUT",
60 headers: { if: `<https://${fixture.hostLabel}.dav.example.com/files/${directory}/> (${token})` },
61 body: "x",
62 }),
63 );
64 expect(allowed.status).toBe(201);
65 });
66 
67 it("creates a readable zero-byte resource for LOCK on an unmapped file URL", async () => {
68 const fixture = await createDavFixture();
69 const file = `${name("unmapped-lock")}.txt`;
70 const response = await fetchWorker(
71 davRequest(fixture, `/files/${file}`, {
72 method: "LOCK",
73 headers: { depth: "0", timeout: "Second-600", "content-type": "application/xml" },
74 body: `<?xml version="1.0"?><D:lockinfo xmlns:D="DAV:"><D:lockscope><D:exclusive/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockinfo>`,
75 }),
76 );
77 
78 expect(response.status).toBe(201);
79 const token = response.headers.get("lock-token");
80 expect(token).toMatch(/^<opaquelocktoken:[^>]+>$/);
81 
82 const get = await fetchWorker(davRequest(fixture, `/files/${file}`));
83 expect(get.status).toBe(200);
84 expect(get.headers.get("content-length")).toBe("0");
85 await expect(get.arrayBuffer()).resolves.toHaveProperty("byteLength", 0);
86 
87 const head = await fetchWorker(davRequest(fixture, `/files/${file}`, { method: "HEAD" }));
88 expect(head.status).toBe(200);
89 expect(head.headers.get("content-length")).toBe("0");
90 
91 const put = await fetchWorker(
92 davRequest(fixture, `/files/${file}`, {
93 method: "PUT",
94 headers: { if: `(${token})` },
95 body: "replaced",
96 }),
97 );
98 expect(put.status).toBe(204);
99 await expect((await fetchWorker(davRequest(fixture, `/files/${file}`))).text()).resolves.toBe("replaced");
100 });
101 
102 it("includes inherited Depth infinity locks in lockdiscovery", async () => {
103 const fixture = await createDavFixture();
104 const directory = name("lockdiscovery");
105 expect(await fetchWorker(davRequest(fixture, `/files/${directory}/`, { method: "MKCOL" }))).toHaveProperty(
106 "status",
107 201,
108 );
109 expect(
110 await fetchWorker(davRequest(fixture, `/files/${directory}/child.txt`, { method: "PUT", body: "x" })),
111 ).toHaveProperty("status", 201);
112 const token = await lock(fixture, `/files/${directory}/`, "infinity");
113 
114 const propfind = await fetchWorker(
115 davRequest(fixture, `/files/${directory}/child.txt`, {
116 method: "PROPFIND",
117 headers: { depth: "0", "content-type": "application/xml" },
118 body: `<?xml version="1.0"?><D:propfind xmlns:D="DAV:"><D:prop><D:lockdiscovery/></D:prop></D:propfind>`,
119 }),
120 );
121 expect(propfind.status).toBe(207);
122 expect(await propfind.text()).toContain(token.slice(1, -1));
123 });
124});