Files
gerbeur/api/test/routes/avatars.test.ts

90 lines
3.1 KiB
TypeScript

import {
createUser,
formWithFile,
get,
hasFfmpeg,
makeImage,
PNG,
put,
test,
webpWidth,
} from "../helpers.ts";
import type { SelfUser } from "../../../shared/user.ts";
import { assertEquals, assertFalse } from "@std/assert";
test("avatars: upload, serve, and 404 when there is none", async () => {
const user = await createUser();
assertEquals((await get(`/api/users/${user.id}/avatar`)).status, 404);
assertEquals(
(await get(`/api/users/${crypto.randomUUID()}/avatar`)).status,
404,
);
const res = await put<SelfUser>("/api/me/avatar", {
auth: user.auth,
form: formWithFile(PNG),
});
assertEquals(res.status, 200);
assertEquals(res.data.avatarMime, "image/png");
assertFalse("passwordHash" in res.data);
const served = await get(`/api/users/${user.id}/avatar`);
assertEquals(served.status, 200);
assertEquals(served.bytes, PNG);
assertEquals(
(await put("/api/me/avatar", {
auth: user.auth,
form: formWithFile(new Uint8Array([1, 2, 3])),
})).status,
400,
);
});
test("avatars: a still holding a second image is scaled to its first alone", async () => {
if (!await hasFfmpeg()) return;
const user = await createUser();
// As a phone's photo carries a gain map after it, which ffmpeg reads as a
// second frame.
const photo = await makeImage(600, 400, "red");
const extra = await makeImage(150, 100, "white");
const both = new Uint8Array(photo.length + extra.length);
both.set(photo);
both.set(extra, photo.length);
await put("/api/me/avatar", { auth: user.auth, form: formWithFile(both) });
const small = await get(`/api/users/${user.id}/avatar?w=128`);
assertEquals(small.headers.get("content-type"), "image/webp");
assertEquals(webpWidth(small.bytes), 128);
assertFalse(new TextDecoder().decode(small.bytes).includes("ANMF"));
});
test("avatars: `?w=` answers a scaled copy, made again from a new avatar", async () => {
if (!await hasFfmpeg()) return;
const user = await createUser();
const red = await makeImage(600, 400, "red");
await put("/api/me/avatar", { auth: user.auth, form: formWithFile(red) });
const small = await get(`/api/users/${user.id}/avatar?w=128`);
assertEquals(small.status, 200);
assertEquals(small.headers.get("content-type"), "image/webp");
assertEquals(small.headers.get("cache-control"), "public, max-age=3600");
assertEquals(webpWidth(small.bytes), 128);
// Never wider than the upload itself.
assertEquals(
webpWidth((await get(`/api/users/${user.id}/avatar?w=960`)).bytes),
600,
);
// Without a width, or with one that isn't a number, the upload goes out.
assertEquals((await get(`/api/users/${user.id}/avatar`)).bytes, red);
assertEquals((await get(`/api/users/${user.id}/avatar?w=big`)).bytes, red);
await put("/api/me/avatar", {
auth: user.auth,
form: formWithFile(await makeImage(600, 400, "blue")),
});
const again = await get(`/api/users/${user.id}/avatar?w=128`);
assertEquals(webpWidth(again.bytes), 128);
assertFalse(
again.bytes.length === small.bytes.length &&
again.bytes.every((b, i) => b === small.bytes[i]),
);
});