Files
internaluploader/server/test/upload.test.ts

73 lines
2.8 KiB
TypeScript
Raw Normal View History

import assert from "node:assert/strict";
import { createHash, randomBytes } from "node:crypto";
import { mkdtempSync } from "node:fs";
import { readFile, readdir, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Readable } from "node:stream";
import { test } from "node:test";
import { UploadDir, hashOf, isValidObjectName } from "../src/upload.ts";
test("object name validation", () => {
const hash = "a".repeat(64);
assert.ok(isValidObjectName(hash));
assert.ok(isValidObjectName(`${hash}.mp4`));
assert.ok(isValidObjectName(`${hash}.webm`));
assert.ok(!isValidObjectName(`${hash}.`));
assert.ok(!isValidObjectName(`${hash}.MP4`), "uppercase extension");
assert.ok(!isValidObjectName(`${hash}.m p4`), "space in extension");
assert.ok(!isValidObjectName(`../../etc/passwd`), "traversal");
assert.ok(!isValidObjectName(`${"a".repeat(63)}.mp4`), "short hash");
assert.ok(!isValidObjectName(`${hash}.${"x".repeat(17)}`), "long extension");
assert.equal(hashOf(`${hash}.mp4`), hash);
});
function payload(bytes: Buffer): { name: string; body: Readable } {
const digest = createHash("sha256").update(bytes).digest("hex");
return { name: `${digest}.bin`, body: Readable.from([bytes]) };
}
test("receive stores a verified upload and is idempotent", async () => {
const dir = mkdtempSync(join(tmpdir(), "uploads-"));
const uploads = new UploadDir(dir, 1024 * 1024);
await uploads.init();
const bytes = randomBytes(4096);
const { name, body } = payload(bytes);
assert.deepEqual(await uploads.receive(name, body), { status: "created" });
assert.deepEqual(await readFile(join(dir, name)), bytes);
assert.ok(await uploads.exists(name));
const again = await uploads.receive(name, Readable.from([bytes]));
assert.deepEqual(again, { status: "exists" });
// The temp dir holds no leftovers either way.
assert.deepEqual(await readdir(join(dir, ".internaluploader-tmp")), []);
});
test("receive rejects a body that does not match its name", async () => {
const dir = mkdtempSync(join(tmpdir(), "uploads-"));
const uploads = new UploadDir(dir, 1024 * 1024);
await uploads.init();
const name = `${"0".repeat(64)}.bin`;
const outcome = await uploads.receive(name, Readable.from([randomBytes(16)]));
assert.equal(outcome.status, "hash-mismatch");
await assert.rejects(stat(join(dir, name)));
assert.deepEqual(await readdir(join(dir, ".internaluploader-tmp")), []);
});
test("receive enforces the size ceiling", async () => {
const dir = mkdtempSync(join(tmpdir(), "uploads-"));
const uploads = new UploadDir(dir, 1000);
await uploads.init();
const bytes = randomBytes(4096);
const { name, body } = payload(bytes);
const outcome = await uploads.receive(name, body);
assert.equal(outcome.status, "too-large");
assert.deepEqual(await readdir(join(dir, ".internaluploader-tmp")), []);
});