Skip to content
File

Blob: tests/worker/control/api/files.workers.test.ts

typescript128 lines
1import { describe, expect, it } from "vitest";
2 
3import {
4 FileListResponseSchema,
5 FileMetadataResponseSchema,
6 MeResponseSchema,
7 PatCreateResponseSchema,
8} from "@/worker/openapi/schemas";
9import { completeOidcLogin } from "@tests/worker/helpers/auth";
10import { basicPat } from "@tests/worker/helpers/dav";
11import { controlRequest, fetchWorker, subjectRequest } from "@tests/worker/helpers/http";
12 
13function jsonHeaders(sessionCookie: string): HeadersInit {
14 return {
15 cookie: sessionCookie,
16 origin: "https://dav.example.com",
17 "content-type": "application/json",
18 };
19}
20 
21async function createFilesPat(sessionCookie: string): Promise<string> {
22 const response = await fetchWorker(
23 controlRequest("/api/v1/pats", {
24 method: "POST",
25 headers: jsonHeaders(sessionCookie),
26 body: JSON.stringify({ name: "files api", scopes: ["files.full"] }),
27 }),
28 );
29 expect(response.status).toBe(201);
30 return PatCreateResponseSchema.parse(await response.json()).token;
31}
32 
33describe("file JSON API", () => {
34 it("wraps FILE_DAV metadata, list, mkdir, rename, and delete operations", async () => {
35 const login = await completeOidcLogin();
36 const meResponse = await fetchWorker(controlRequest("/api/v1/me", { headers: { cookie: login.sessionCookie } }));
37 const me = MeResponseSchema.parse(await meResponse.json());
38 const token = await createFilesPat(login.sessionCookie);
39 const auth = basicPat(token);
40 const directory = `api-${crypto.randomUUID()}`;
41 
42 const mkdir = await fetchWorker(
43 controlRequest("/api/v1/files/mkdir", {
44 method: "POST",
45 headers: jsonHeaders(login.sessionCookie),
46 body: JSON.stringify({ path: directory }),
47 }),
48 );
49 expect(mkdir.status).toBe(201);
50 expect(FileMetadataResponseSchema.parse(await mkdir.json())).toMatchObject({
51 href: `/files/${directory}/`,
52 kind: "collection",
53 });
54 
55 const put = await fetchWorker(
56 subjectRequest(me.host_label, `/files/${directory}/note.txt`, {
57 method: "PUT",
58 headers: { authorization: auth, "content-type": "text/plain" },
59 body: "hello",
60 }),
61 );
62 expect(put.status).toBe(201);
63 
64 const metadata = await fetchWorker(
65 controlRequest(`/api/v1/files/metadata?path=${encodeURIComponent(`${directory}/note.txt`)}`, {
66 headers: { cookie: login.sessionCookie },
67 }),
68 );
69 expect(metadata.status).toBe(200);
70 expect(FileMetadataResponseSchema.parse(await metadata.json())).toMatchObject({
71 href: `/files/${directory}/note.txt`,
72 name: "note.txt",
73 kind: "file",
74 size: 5,
75 content_type: "text/plain",
76 });
77 
78 const list = await fetchWorker(
79 controlRequest(`/api/v1/files/list?path=${encodeURIComponent(directory)}`, {
80 headers: { cookie: login.sessionCookie },
81 }),
82 );
83 expect(list.status).toBe(200);
84 const listed = FileListResponseSchema.parse(await list.json());
85 expect(listed.directory.href).toBe(`/files/${directory}/`);
86 expect(listed.entries.map((entry) => entry.href)).toContain(`/files/${directory}/note.txt`);
87 
88 const rename = await fetchWorker(
89 controlRequest("/api/v1/files/rename", {
90 method: "POST",
91 headers: jsonHeaders(login.sessionCookie),
92 body: JSON.stringify({ from: `${directory}/note.txt`, to: `${directory}/renamed.txt` }),
93 }),
94 );
95 expect(rename.status).toBe(200);
96 expect(FileMetadataResponseSchema.parse(await rename.json())).toMatchObject({
97 href: `/files/${directory}/renamed.txt`,
98 name: "renamed.txt",
99 });
100 
101 const oldPath = await fetchWorker(
102 subjectRequest(me.host_label, `/files/${directory}/note.txt`, { headers: { authorization: auth } }),
103 );
104 expect(oldPath.status).toBe(404);
105 const renamed = await fetchWorker(
106 subjectRequest(me.host_label, `/files/${directory}/renamed.txt`, { headers: { authorization: auth } }),
107 );
108 expect(renamed.status).toBe(200);
109 await expect(renamed.text()).resolves.toBe("hello");
110 
111 const deleted = await fetchWorker(
112 controlRequest("/api/v1/files/delete", {
113 method: "DELETE",
114 headers: jsonHeaders(login.sessionCookie),
115 body: JSON.stringify({ path: `${directory}/renamed.txt` }),
116 }),
117 );
118 expect(deleted.status).toBe(204);
119 
120 const afterDelete = await fetchWorker(
121 controlRequest(`/api/v1/files/metadata?path=${encodeURIComponent(`${directory}/renamed.txt`)}`, {
122 headers: { cookie: login.sessionCookie },
123 }),
124 );
125 expect(afterDelete.status).toBe(404);
126 });
127});