Public: anyone can read this; the crew can change it.

A test pins what a person's attached look accepts: `readImage` and `cleanNote` at their edges #6770

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6749-a-test-pins-what-a-person-s-attached-loo → main 1 commit fork

  • Merged. scout/6749-a-test-pins-what-a-person-s-attached-loo was merged into main as 0479024.

Review

Sign in to review this change.
1 file changed +110 −0
test/attach-look-edges.test.ts added +110
@@ -0,0 +1,110 @@
1 /**
2 * What a person's attached look accepts, at its edges (#1066): `readImage`
3 * reads base64 as bytes and refuses what is not a PNG or JPEG of a size we
4 * hold; `cleanNote` keeps one short line, counted in code points.
5 */
6
7 import { describe, expect, test } from "bun:test";
8 import {
9 ATTACH_MAX_BYTES,
10 ATTACH_NOTE_MAX,
11 cleanNote,
12 readImage,
13 } from "../src/actions/attach_look.ts";
14 import { encodePng } from "../src/design/image/png.ts";
15
16 const b64 = (bytes: Uint8Array) => Buffer.from(bytes).toString("base64");
17
18 /** Just enough PNG to read its header: signature and a 13-byte IHDR. */
19 function pngHead(width: number, height: number): Uint8Array {
20 const out = new Uint8Array(33);
21 out.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
22 const v = new DataView(out.buffer);
23 v.setUint32(8, 13);
24 out.set([0x49, 0x48, 0x44, 0x52], 12);
25 v.setUint32(16, width);
26 v.setUint32(20, height);
27 out[24] = 8;
28 out[25] = 6;
29 return out;
30 }
31
32 /** SOI, then a baseline start-of-frame of one component. */
33 function jpegHead(width: number, height: number): Uint8Array {
34 return Uint8Array.from([
35 0xff, 0xd8, 0xff, 0xc0, 0x00, 0x0b, 8,
36 height >> 8, height & 0xff, width >> 8, width & 0xff,
37 1, 1, 0x11, 0,
38 ]);
39 }
40
41 describe("readImage", () => {
42 test("base64 that is not a multiple of 4, or has a stray character, is bad_image", () => {
43 const good = b64(encodePng(1, 1, new Uint8Array(4)));
44 expect(readImage(good).ok).toBe(true);
45 for (const bad of [good.replace(/=+$/, "").slice(0, -1) + "A", "abc", "QUJD*A==", "QU JD", "", "=AAA", "AAAA\n"]) {
46 const r = readImage(bad);
47 expect(r).toEqual({ ok: false, error: "bad_image" } as never);
48 }
49 });
50
51 test("a string past the length bound is too_large before any decode", () => {
52 // Not even base64: only the length check can have refused it.
53 const long = "*".repeat(Math.ceil((ATTACH_MAX_BYTES * 4) / 3) + 5);
54 const r = readImage(long);
55 expect(r.ok).toBe(false);
56 if (!r.ok) expect(r.error).toBe("too_large");
57 });
58
59 test("a minimal PNG and a minimal JPEG come back with ext and size", () => {
60 const png = readImage(b64(encodePng(3, 2, new Uint8Array(24))));
61 expect(png.ok).toBe(true);
62 if (png.ok) expect([png.value.ext, png.value.width, png.value.height]).toEqual(["png", 3, 2]);
63 const jpg = readImage(b64(jpegHead(5, 7)));
64 expect(jpg.ok).toBe(true);
65 if (jpg.ok) expect([jpg.value.ext, jpg.value.width, jpg.value.height]).toEqual(["jpg", 5, 7]);
66 });
67
68 test("a GIF header is bad_image", () => {
69 const gif = Uint8Array.from([...Buffer.from("GIF89a"), 1, 0, 1, 0, 0, 0, 0, 0x3b]);
70 const r = readImage(b64(gif));
71 expect(r.ok).toBe(false);
72 if (!r.ok) expect(r.error).toBe("bad_image");
73 });
74
75 test("a PNG claiming a side over the limit is bad_image; the limit itself is kept", () => {
76 for (const [w, h] of [[8193, 1], [1, 8193]] as const) {
77 const r = readImage(b64(pngHead(w, h)));
78 expect(r.ok).toBe(false);
79 if (!r.ok) expect(r.error).toBe("bad_image");
80 }
81 expect(readImage(b64(pngHead(8192, 8192))).ok).toBe(true);
82 });
83 });
84
85 describe("cleanNote", () => {
86 const bad = (raw: string) => {
87 const r = cleanNote(raw);
88 expect(r.ok).toBe(false);
89 if (!r.ok) expect(r.error).toBe("bad_note");
90 };
91
92 test("trims", () => {
93 const r = cleanNote(" the phone view \t");
94 expect(r.ok && r.value).toBe("the phone view");
95 });
96
97 test("a blank note, a newline and a DEL are bad_note", () => {
98 bad("");
99 bad(" \t ");
100 bad("two\nlines");
101 bad("del\u007fchar");
102 });
103
104 test("the bound counts code points, not UTF-16 units", () => {
105 const ok = cleanNote("😀".repeat(ATTACH_NOTE_MAX));
106 expect(ok.ok).toBe(true);
107 bad("😀".repeat(ATTACH_NOTE_MAX + 1));
108 bad("a".repeat(ATTACH_NOTE_MAX + 1));
109 });
110 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs