yap-blog/tests/unit/uploads.test.ts
2026-07-02 21:32:33 -04:00

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 });
});
});