g1t/apps/web/app/lib/avatar-upload.test.ts
Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Workspace names and icons, and a component kit for every control | 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 | }); |