Files

98 lines
3.3 KiB
TypeScript
Raw Permalink Normal View History

2026-10-02 14:20:43 +02:00
import { describe, expect, test } from "bun:test";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2026-10-23 18:23:49 +02:00
2026-10-02 14:20:43 +02:00
import { createJob, createUser, jobDirs, request, uploadsDir } from "./helpers/app";
async function userWithUploads(...fileNames: string[]) {
const user = await createUser();
const job = createJob(user.id);
const dirs = jobDirs(user.id, job);
mkdirSync(dirs.uploads, { recursive: true });
for (const fileName of fileNames) {
writeFileSync(`${dirs.uploads}${fileName}`, fileName);
}
return { user, job, dirs };
}
describe("POST /delete", () => {
test("requires a session", async () => {
const response = await request("/delete", {
headers: { accept: "application/json" },
json: { filename: "a.txt" },
});
expect(response.status).toBe(401);
});
test("redirects home without a job", async () => {
const { user, dirs } = await userWithUploads("a.txt");
const response = await request("/delete", {
cookies: { auth: user.token },
json: { filename: "a.txt" },
});
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe("/");
expect(existsSync(`${dirs.uploads}a.txt`)).toBe(true);
});
test("refuses to delete from another user's job", async () => {
const { user } = await userWithUploads();
const other = await userWithUploads("a.txt");
const response = await request("/delete", {
cookies: { auth: user.token, jobId: other.job },
json: { filename: "a.txt" },
});
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe("/");
expect(existsSync(`${other.dirs.uploads}a.txt`)).toBe(true);
});
test("deletes only the given uploaded file", async () => {
const { user, job, dirs } = await userWithUploads("a.txt", "b.txt");
const response = await request("/delete", {
cookies: { auth: user.token, jobId: job },
json: { filename: "a.txt" },
});
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ message: "File deleted successfully." });
expect(existsSync(`${dirs.uploads}a.txt`)).toBe(false);
expect(existsSync(`${dirs.uploads}b.txt`)).toBe(true);
});
test("sanitizes the file name so files outside the job cannot be deleted", async () => {
const { user, job } = await userWithUploads();
const other = await userWithUploads("keep.txt");
2026-10-02 15:27:37 +02:00
const response = await request("/delete", {
2026-10-02 14:20:43 +02:00
cookies: { auth: user.token, jobId: job },
json: { filename: `../../${other.user.id}/${other.job}/keep.txt` },
});
2026-10-02 15:27:37 +02:00
// the request passed the session and job checks, so the file name was really used;
// the sanitized name does not exist, which runs into the bug tracked below
expect(response.status).not.toBeOneOf([302, 401]);
2026-10-02 14:20:43 +02:00
expect(existsSync(`${other.dirs.uploads}keep.txt`)).toBe(true);
});
test.each(["missing.txt", "../../1/1/keep.txt"])(
2026-10-02 15:27:37 +02:00
"answers a missing file (%s) without a server error or server paths",
async (filename) => {
const { user, job } = await userWithUploads();
const response = await request("/delete", {
cookies: { auth: user.token, jobId: job },
json: { filename },
});
expect(response.status).toBeLessThan(500);
expect(await response.text()).not.toContain(uploadsDir);
},
);
2026-10-02 14:20:43 +02:00
});