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);
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-22 16:56:38 +02:00
|
|
|
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
|
|
|
});
|