mirror of
https://github.com/C4illin/ConvertX.git
synced 2026-10-28 07:44:21 +01:00
2135c6b9f1
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
173 lines
5.6 KiB
TypeScript
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("/");
|
|
});
|
|
});
|