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("/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]), ); });