Files
ConvertX/tests/pages/convert.test.ts
Emrik Östling 2135c6b9f1 chore: use bun fmt and bun check (#685)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-10-23 18:23:49 +02:00

173 lines
5.6 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { mkdirSync } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import { createJob, createUser, db, jobDirs, request, waitFor } from "./helpers/app";
type JobRow = { status: string; num_files: number };
type FileRow = { file_name: string; output_file_name: string; status: string };
function jobRow(jobId: number) {
return db.query("SELECT status, num_files FROM jobs WHERE id = ?").get(jobId) as JobRow;
}
function fileRows(jobId: number) {
return db
.query(
"SELECT file_name, output_file_name, status FROM file_names WHERE job_id = ? ORDER BY file_name",
)
.all(jobId) as FileRow[];
}
async function userWithJob() {
const user = await createUser();
const job = createJob(user.id);
return { user, job, dirs: jobDirs(user.id, job) };
}
function convertRequest(
cookies: { auth: string; jobId?: number },
form: { convert_to: string; file_names: string },
) {
return request("/convert", { cookies, form });
}
describe("POST /convert", () => {
test("requires a session", async () => {
const response = await request("/convert", {
headers: { accept: "application/json" },
form: { convert_to: "csv,vcf", file_names: "[]" },
});
expect(response.status).toBe(401);
});
test("redirects home without a job", async () => {
const { user } = await userWithJob();
const response = await convertRequest(
{ auth: user.token },
{ convert_to: "csv,vcf", file_names: '["a.vcf"]' },
);
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe("/");
});
test("refuses to convert another user's job", async () => {
const { user } = await userWithJob();
const other = await userWithJob();
const response = await convertRequest(
{ auth: user.token, jobId: other.job },
{ convert_to: "csv,vcf", file_names: '["a.vcf"]' },
);
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe("/");
expect(jobRow(other.job).status).toBe("not started");
});
test.each([
["no converter is given", "csv"],
["the target contains a path separator", "a/b,vcf"],
["the target contains a backslash", "a\\b,vcf"],
["the target points to a parent directory", "..,vcf"],
])("redirects home without converting when %s", async (_, convertTo) => {
const { user, job } = await userWithJob();
const response = await convertRequest(
{ auth: user.token, jobId: job },
{ convert_to: convertTo, file_names: '["a.vcf"]' },
);
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe("/");
expect(jobRow(job).status).toBe("not started");
});
test("redirects home without converting when no files are given", async () => {
const { user, job } = await userWithJob();
const response = await convertRequest(
{ auth: user.token, jobId: job },
{ convert_to: "csv,vcf", file_names: "[]" },
);
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe("/");
expect(jobRow(job).status).toBe("not started");
});
test("converts the uploaded files in the background and shows the results", async () => {
const { user, job, dirs } = await userWithJob();
mkdirSync(dirs.uploads, { recursive: true });
await writeFile(`${dirs.uploads}a.vcf`, "BEGIN:VCARD\nFN:Alice\nEND:VCARD\n");
await writeFile(`${dirs.uploads}b.vcf`, "BEGIN:VCARD\nFN:Bob\nEND:VCARD\n");
const response = await convertRequest(
{ auth: user.token, jobId: job },
{ convert_to: "csv,vcf", file_names: '["a.vcf","b.vcf"]' },
);
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe(`/results/${job}`);
expect(jobRow(job).num_files).toBe(2);
await waitFor(() => jobRow(job).status === "completed");
expect(fileRows(job)).toEqual([
{ file_name: "a.vcf", output_file_name: "a.csv", status: "Done" },
{ file_name: "b.vcf", output_file_name: "b.csv", status: "Done" },
]);
expect(await readFile(`${dirs.output}a.csv`, "utf-8")).toBe('Full Name\n"Alice"');
});
test("normalizes the target type before converting", async () => {
const { user, job } = await userWithJob();
await convertRequest(
{ auth: user.token, jobId: job },
{ convert_to: "JPG,doesnotexist", file_names: '["photo.png"]' },
);
await waitFor(() => jobRow(job).status === "completed");
// jpg is normalized to jpeg, whose files get the .jpg extension
expect(fileRows(job)).toEqual([
{ file_name: "photo.png", output_file_name: "photo.jpg", status: "File type not supported" },
]);
});
test("sanitizes file names so conversions cannot read outside the job directory", async () => {
const { user, job } = await userWithJob();
await convertRequest(
{ auth: user.token, jobId: job },
{ convert_to: "csv,vcf", file_names: '["../../secret.vcf"]' },
);
await waitFor(() => jobRow(job).status === "completed");
expect(fileRows(job)).toEqual([
{
file_name: "....secret.vcf",
output_file_name: "....secret.csv",
status: "Failed, check logs",
},
]);
});
test.each([
["is not JSON", "not json"],
["is a JSON string", '"a.vcf"'],
])("redirects home when file_names %s", async (_, fileNames) => {
const { user, job } = await userWithJob();
const response = await convertRequest(
{ auth: user.token, jobId: job },
{ convert_to: "csv,vcf", file_names: fileNames },
);
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe("/");
});
});