66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
import { mkdtemp, readdir, rm, stat } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { MAX_UPLOAD_BYTES, saveUploadedImage } from "@/lib/uploads";
|
|
|
|
const PNG_BYTES = Buffer.from(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
|
|
"base64",
|
|
);
|
|
|
|
const originalCwd = process.cwd();
|
|
let rootDir: string;
|
|
let uploadsDir: string;
|
|
beforeEach(async () => {
|
|
rootDir = await mkdtemp(path.join(tmpdir(), "blog-uploads-"));
|
|
uploadsDir = path.join(rootDir, "uploads");
|
|
process.chdir(rootDir);
|
|
});
|
|
afterEach(async () => {
|
|
process.chdir(originalCwd);
|
|
await rm(rootDir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe("saveUploadedImage", () => {
|
|
it("saves a valid image under a random name with the right extension", async () => {
|
|
const file = new File([PNG_BYTES], "user chosen name!!.png", { type: "image/png" });
|
|
const result = await saveUploadedImage(file);
|
|
expect(result.ok).toBe(true);
|
|
if (!result.ok) return;
|
|
|
|
expect(result.filename).toMatch(/^[0-9a-f-]{36}\.png$/);
|
|
const written = await stat(path.join(uploadsDir, result.filename));
|
|
expect(written.size).toBe(PNG_BYTES.length);
|
|
// The client-supplied filename never reaches the filesystem.
|
|
expect(await readdir(uploadsDir)).toEqual([result.filename]);
|
|
});
|
|
|
|
it("maps jpeg MIME to a .jpg extension", async () => {
|
|
const file = new File([PNG_BYTES], "x", { type: "image/jpeg" });
|
|
const result = await saveUploadedImage(file);
|
|
expect(result.ok && result.filename.endsWith(".jpg")).toBe(true);
|
|
});
|
|
|
|
it("rejects non-image MIME types", async () => {
|
|
const file = new File(["<svg onload=alert(1)>"], "evil.svg", { type: "image/svg+xml" });
|
|
const result = await saveUploadedImage(file);
|
|
expect(result).toMatchObject({ ok: false, status: 400 });
|
|
await expect(readdir(uploadsDir)).rejects.toMatchObject({ code: "ENOENT" });
|
|
});
|
|
|
|
it("rejects files over the size limit", async () => {
|
|
const big = new File([Buffer.alloc(MAX_UPLOAD_BYTES + 1)], "big.png", {
|
|
type: "image/png",
|
|
});
|
|
const result = await saveUploadedImage(big);
|
|
expect(result).toMatchObject({ ok: false, status: 413 });
|
|
});
|
|
|
|
it("rejects empty files", async () => {
|
|
const empty = new File([], "empty.png", { type: "image/png" });
|
|
const result = await saveUploadedImage(empty);
|
|
expect(result).toMatchObject({ ok: false, status: 400 });
|
|
});
|
|
});
|