g1t/apps/web/app/lib/avatar-upload.test.ts
| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { MAX_AVATAR_BYTES, readAvatarUpload, sniffImage, toBase64 } from "./avatar-upload.ts"; |
| 5 | |
| 6 | const PNG = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0, 0, 0, 13]); |
| 7 | const JPEG = new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0, 16]); |
| 8 | const text = (value: string) => new TextEncoder().encode(value); |
| 9 | |
| 10 | function form(bytes: Uint8Array, name = "icon.png", type = "image/png"): FormData { |
| 11 | const data = new FormData(); |
| 12 | data.set("avatar", new File([bytes], name, { type })); |
| 13 | return data; |
| 14 | } |
| 15 | |
| 16 | test("an image is known by its bytes", () => { |
| 17 | assert.equal(sniffImage(PNG), "image/png"); |
| 18 | assert.equal(sniffImage(JPEG), "image/jpeg"); |
| 19 | assert.equal(sniffImage(text("GIF89a\x01\x00")), "image/gif"); |
| 20 | assert.equal(sniffImage(text("RIFF\x24\x00\x00\x00WEBPVP8 ")), "image/webp"); |
| 21 | }); |
| 22 | |
| 23 | test("SVG and anything else is refused, whatever it is called", async () => { |
| 24 | assert.equal(sniffImage(text('<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>')), null); |
| 25 | assert.equal(sniffImage(text("RIFF\x00\x00\x00\x00WAVEfmt ")), null); |
| 26 | const disguised = await readAvatarUpload(form(text("<svg onload=alert(1)/>"), "icon.png", "image/png")); |
| 27 | assert.ok("error" in disguised); |
| 28 | }); |
| 29 | |
| 30 | test("more than a megabyte is refused", async () => { |
| 31 | const big = new Uint8Array(MAX_AVATAR_BYTES + 1); |
| 32 | big.set(PNG); |
| 33 | const result = await readAvatarUpload(form(big)); |
| 34 | assert.deepEqual(result, { error: "Use an image of at most 1 MB." }); |
| 35 | const exact = new Uint8Array(MAX_AVATAR_BYTES); |
| 36 | exact.set(PNG); |
| 37 | const fits = await readAvatarUpload(form(exact)); |
| 38 | assert.ok("image" in fits); |
| 39 | }); |
| 40 | |
| 41 | test("an image is sent as base64", async () => { |
| 42 | const result = await readAvatarUpload(form(PNG)); |
| 43 | assert.deepEqual(result, { image: toBase64(PNG) }); |
| 44 | assert.equal(Buffer.from(toBase64(PNG), "base64").compare(Buffer.from(PNG)), 0); |
| 45 | }); |
| 46 | |
| 47 | test("a missing file is asked for", async () => { |
| 48 | assert.deepEqual(await readAvatarUpload(new FormData()), { error: "Choose an image to upload." }); |
| 49 | }); |