135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
import {
|
|
createUser,
|
|
db,
|
|
get,
|
|
insertDump,
|
|
PNG,
|
|
test,
|
|
TEST_DIR,
|
|
} from "../helpers.ts";
|
|
import { assertEquals } from "@std/assert";
|
|
|
|
const BYTES = new TextEncoder().encode("0123456789");
|
|
|
|
function createdAtOf(id: string): string {
|
|
return (db.prepare(`SELECT created_at FROM dumps WHERE id = ?;`).get(id) as {
|
|
created_at: string;
|
|
}).created_at;
|
|
}
|
|
|
|
async function hasFfmpeg(): Promise<boolean> {
|
|
try {
|
|
return (await new Deno.Command("ffmpeg", {
|
|
args: ["-version"],
|
|
stdout: "null",
|
|
stderr: "null",
|
|
}).output()).success;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function readDir(path: string): string[] {
|
|
try {
|
|
return [...Deno.readDirSync(path)].map((e) => e.name);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
test("GET /api/dumps/:id/thumbnail: serves a cached video still, with privacy", async () => {
|
|
const owner = await createUser();
|
|
const video = insertDump({
|
|
userId: owner.id,
|
|
fileName: "v.mp4",
|
|
fileMime: "video/mp4",
|
|
});
|
|
const priv = insertDump({
|
|
userId: owner.id,
|
|
fileName: "p.mp4",
|
|
fileMime: "video/mp4",
|
|
isPrivate: true,
|
|
});
|
|
Deno.mkdirSync(`${TEST_DIR}/uploads/thumbnails`, { recursive: true });
|
|
for (const { id } of [video, priv]) {
|
|
Deno.writeFileSync(`${TEST_DIR}/uploads/thumbnails/${id}.jpg`, PNG);
|
|
}
|
|
const res = await get(`/api/dumps/${video.id}/thumbnail`);
|
|
assertEquals(res.status, 200);
|
|
assertEquals(res.headers.get("content-type"), "image/jpeg");
|
|
assertEquals(res.headers.get("cache-control"), "public, no-cache");
|
|
const v = (id: string) => `5-${Date.parse(createdAtOf(id))}`;
|
|
assertEquals(
|
|
(await get(`/api/dumps/${video.id}/thumbnail?v=${v(video.id)}`)).headers
|
|
.get(
|
|
"cache-control",
|
|
),
|
|
"public, max-age=31536000, immutable",
|
|
);
|
|
assertEquals((await get(`/api/dumps/${priv.id}/thumbnail`)).status, 404);
|
|
const own = await get(
|
|
`/api/dumps/${priv.id}/thumbnail?v=${v(priv.id)}&token=${owner.mediaToken}`,
|
|
);
|
|
assertEquals(own.status, 200);
|
|
assertEquals(
|
|
own.headers.get("cache-control"),
|
|
"private, max-age=31536000, immutable",
|
|
);
|
|
|
|
const text = insertDump({ userId: owner.id, fileName: "t.txt" });
|
|
assertEquals((await get(`/api/dumps/${text.id}/thumbnail`)).status, 404);
|
|
assertEquals((await get(`/api/dumps/nope/thumbnail`)).status, 400);
|
|
});
|
|
|
|
test("GET /api/dumps/:id/thumbnail: an image dump gets a preview, a GIF itself", async () => {
|
|
const owner = await createUser();
|
|
const gif = new TextEncoder().encode("GIF89a-not-really");
|
|
const animated = insertDump({
|
|
userId: owner.id,
|
|
fileName: "a.gif",
|
|
fileMime: "image/gif",
|
|
fileData: gif,
|
|
});
|
|
const res = await get(`/api/dumps/${animated.id}/thumbnail`);
|
|
assertEquals(res.status, 200);
|
|
assertEquals(res.headers.get("content-type"), "image/gif");
|
|
assertEquals(res.bytes, gif);
|
|
|
|
// A real PNG is scaled into a preview, kept beside the video stills.
|
|
const photo = insertDump({
|
|
userId: owner.id,
|
|
fileName: "p.png",
|
|
fileMime: "image/png",
|
|
fileData: PNG,
|
|
});
|
|
const preview = await get(`/api/dumps/${photo.id}/thumbnail`);
|
|
assertEquals(preview.status, 200);
|
|
assertEquals(preview.headers.get("content-type"), "image/png");
|
|
if (await hasFfmpeg()) {
|
|
assertEquals(
|
|
readDir(`${TEST_DIR}/uploads/thumbnails`).filter((n) =>
|
|
n.startsWith(photo.id)
|
|
),
|
|
[`${photo.id}.preview.png`],
|
|
);
|
|
}
|
|
|
|
// Not an image ffmpeg can read (or no ffmpeg at all): the original goes out.
|
|
const broken = insertDump({
|
|
userId: owner.id,
|
|
fileName: "b.png",
|
|
fileMime: "image/png",
|
|
fileData: BYTES,
|
|
});
|
|
const fallback = await get(`/api/dumps/${broken.id}/thumbnail`);
|
|
assertEquals(fallback.status, 200);
|
|
assertEquals(fallback.headers.get("content-type"), "image/png");
|
|
assertEquals(fallback.bytes, BYTES);
|
|
assertEquals(
|
|
readDir(`${TEST_DIR}/uploads/thumbnails`).filter((n) =>
|
|
n.startsWith(broken.id)
|
|
),
|
|
[],
|
|
);
|
|
});
|