File
Blob: tests/worker/protocol/webdav/if-header.test.ts
| 1 | import { describe, expect, it } from "vitest"; |
| 2 | |
| 3 | import { parseIfHeader, positiveEtags, positiveLockTokens } from "@/worker/dav/if-header"; |
| 4 | import { ifHeaderMatches } from "@/worker/dav/locks"; |
| 5 | import type { FileLockRow } from "@/worker/db/file-dav-do/schema"; |
| 6 | |
| 7 | function activeLock(input: { token?: string; rootPath: string; depth?: "0" | "infinity" }): FileLockRow { |
| 8 | return { |
| 9 | token: input.token ?? "opaquelocktoken:abc", |
| 10 | rootNodeId: "node-1", |
| 11 | rootPath: input.rootPath, |
| 12 | ownerXml: '<D:owner xmlns:D="DAV:"/>', |
| 13 | principalSubjectId: "subject-1", |
| 14 | scope: "exclusive", |
| 15 | depth: input.depth ?? "0", |
| 16 | createdAtMs: 1, |
| 17 | expiresAtMs: 2, |
| 18 | }; |
| 19 | } |
| 20 | |
| 21 | describe("WebDAV If header parser", () => { |
| 22 | it("parses untagged lock tokens and entity tags", () => { |
| 23 | const parsed = parseIfHeader('(<opaquelocktoken:abc> ["etag-1"])'); |
| 24 | expect(parsed.ok).toBe(true); |
| 25 | if (!parsed.ok) return; |
| 26 | expect(positiveLockTokens(parsed.header)).toEqual(["opaquelocktoken:abc"]); |
| 27 | expect(positiveEtags(parsed.header)).toEqual(['"etag-1"']); |
| 28 | }); |
| 29 | |
| 30 | it("parses tagged lists and Not conditions without treating prefixes as text hacks", () => { |
| 31 | const parsed = parseIfHeader("</files/a.txt> (Not <opaquelocktoken:abc>) (<opaquelocktoken:def>)"); |
| 32 | expect(parsed.ok).toBe(true); |
| 33 | if (!parsed.ok) return; |
| 34 | expect(parsed.header.lists[0]?.resourceHref).toBe("/files/a.txt"); |
| 35 | expect(parsed.header.lists[0]?.conditions[0]).toMatchObject({ not: true, kind: "lock-token" }); |
| 36 | expect(positiveLockTokens(parsed.header)).toEqual(["opaquelocktoken:def"]); |
| 37 | }); |
| 38 | |
| 39 | it("matches absolute tagged lock roots against descendant writes", () => { |
| 40 | const parsed = parseIfHeader("<https://example.test/files/locked/> (<opaquelocktoken:abc>)"); |
| 41 | expect(parsed.ok).toBe(true); |
| 42 | if (!parsed.ok) return; |
| 43 | |
| 44 | expect( |
| 45 | ifHeaderMatches({ |
| 46 | header: parsed.header, |
| 47 | targetHref: "/files/locked/child.txt", |
| 48 | targetEtag: null, |
| 49 | locks: [activeLock({ rootPath: "/files/locked/", depth: "infinity" })], |
| 50 | }), |
| 51 | ).toBe(true); |
| 52 | }); |
| 53 | |
| 54 | it("honors Not conditions for entity tags", () => { |
| 55 | const parsed = parseIfHeader('(Not ["other-etag"])'); |
| 56 | expect(parsed.ok).toBe(true); |
| 57 | if (!parsed.ok) return; |
| 58 | |
| 59 | expect( |
| 60 | ifHeaderMatches({ |
| 61 | header: parsed.header, |
| 62 | targetHref: "/files/a.txt", |
| 63 | targetEtag: '"etag-1"', |
| 64 | locks: [], |
| 65 | }), |
| 66 | ).toBe(true); |
| 67 | }); |
| 68 | }); |