import { describe, expect, it } from "vitest"; import { FileListResponseSchema, FileMetadataResponseSchema, MeResponseSchema, PatCreateResponseSchema, } from "@/worker/openapi/schemas"; import { completeOidcLogin } from "@tests/worker/helpers/auth"; import { basicPat } from "@tests/worker/helpers/dav"; import { controlRequest, fetchWorker, subjectRequest } from "@tests/worker/helpers/http"; function jsonHeaders(sessionCookie: string): HeadersInit { return { cookie: sessionCookie, origin: "https://dav.example.com", "content-type": "application/json", }; } async function createFilesPat(sessionCookie: string): Promise { const response = await fetchWorker( controlRequest("/api/v1/pats", { method: "POST", headers: jsonHeaders(sessionCookie), body: JSON.stringify({ name: "files api", scopes: ["files.full"] }), }), ); expect(response.status).toBe(201); return PatCreateResponseSchema.parse(await response.json()).token; } describe("file JSON API", () => { it("wraps FILE_DAV metadata, list, mkdir, rename, and delete operations", async () => { const login = await completeOidcLogin(); const meResponse = await fetchWorker(controlRequest("/api/v1/me", { headers: { cookie: login.sessionCookie } })); const me = MeResponseSchema.parse(await meResponse.json()); const token = await createFilesPat(login.sessionCookie); const auth = basicPat(token); const directory = `api-${crypto.randomUUID()}`; const mkdir = await fetchWorker( controlRequest("/api/v1/files/mkdir", { method: "POST", headers: jsonHeaders(login.sessionCookie), body: JSON.stringify({ path: directory }), }), ); expect(mkdir.status).toBe(201); expect(FileMetadataResponseSchema.parse(await mkdir.json())).toMatchObject({ href: `/files/${directory}/`, kind: "collection", }); const put = await fetchWorker( subjectRequest(me.host_label, `/files/${directory}/note.txt`, { method: "PUT", headers: { authorization: auth, "content-type": "text/plain" }, body: "hello", }), ); expect(put.status).toBe(201); const metadata = await fetchWorker( controlRequest(`/api/v1/files/metadata?path=${encodeURIComponent(`${directory}/note.txt`)}`, { headers: { cookie: login.sessionCookie }, }), ); expect(metadata.status).toBe(200); expect(FileMetadataResponseSchema.parse(await metadata.json())).toMatchObject({ href: `/files/${directory}/note.txt`, name: "note.txt", kind: "file", size: 5, content_type: "text/plain", }); const list = await fetchWorker( controlRequest(`/api/v1/files/list?path=${encodeURIComponent(directory)}`, { headers: { cookie: login.sessionCookie }, }), ); expect(list.status).toBe(200); const listed = FileListResponseSchema.parse(await list.json()); expect(listed.directory.href).toBe(`/files/${directory}/`); expect(listed.entries.map((entry) => entry.href)).toContain(`/files/${directory}/note.txt`); const rename = await fetchWorker( controlRequest("/api/v1/files/rename", { method: "POST", headers: jsonHeaders(login.sessionCookie), body: JSON.stringify({ from: `${directory}/note.txt`, to: `${directory}/renamed.txt` }), }), ); expect(rename.status).toBe(200); expect(FileMetadataResponseSchema.parse(await rename.json())).toMatchObject({ href: `/files/${directory}/renamed.txt`, name: "renamed.txt", }); const oldPath = await fetchWorker( subjectRequest(me.host_label, `/files/${directory}/note.txt`, { headers: { authorization: auth } }), ); expect(oldPath.status).toBe(404); const renamed = await fetchWorker( subjectRequest(me.host_label, `/files/${directory}/renamed.txt`, { headers: { authorization: auth } }), ); expect(renamed.status).toBe(200); await expect(renamed.text()).resolves.toBe("hello"); const deleted = await fetchWorker( controlRequest("/api/v1/files/delete", { method: "DELETE", headers: jsonHeaders(login.sessionCookie), body: JSON.stringify({ path: `${directory}/renamed.txt` }), }), ); expect(deleted.status).toBe(204); const afterDelete = await fetchWorker( controlRequest(`/api/v1/files/metadata?path=${encodeURIComponent(`${directory}/renamed.txt`)}`, { headers: { cookie: login.sessionCookie }, }), ); expect(afterDelete.status).toBe(404); }); });