| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | import { DEBUGGER_IDLE_MS } from "./debuggerLease.js"; |
| 4 | import { crc32, deflateSync } from "node:zlib"; |
| 5 | import { FakePage } from "./fakeGuestViews.js"; |
| 6 | import { captureScreenshot } from "./screenshot.js"; |
| 7 | import { validatePNG } from "./png.js"; |
| 8 | |
| 9 | function png() { |
| 10 | const chunk = (kind: string, data: Buffer) => { |
| 11 | const out = Buffer.alloc(data.length + 12); |
| 12 | out.writeUInt32BE(data.length); out.write(kind, 4); data.copy(out, 8); |
| 13 | out.writeUInt32BE(crc32(out.subarray(4, -4)), out.length - 4); |
| 14 | return out; |
| 15 | }; |
| 16 | const header = Buffer.alloc(13); |
| 17 | header.writeUInt32BE(1); header.writeUInt32BE(1, 4); header[8] = 8; header[9] = 6; |
| 18 | return Buffer.concat([Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), chunk("IHDR", header), chunk("IDAT", deflateSync(Buffer.from([0, 255, 0, 0, 255]))), chunk("IEND", Buffer.alloc(0))]); |
| 19 | } |
| 20 | |
| 21 | test("captures are validated before any file is published", async () => { |
| 22 | const page = new FakePage(1); |
| 23 | let writes = 0; |
| 24 | const deps = { directoryExists: () => true, writeFile: () => { writes++; } }; |
| 25 | const request = { ref: "", fullPage: false, directory: "/scratch" }; |
| 26 | for (const data of [Buffer.alloc(0), Buffer.from("png"), png().subarray(0, 33)]) { |
| 27 | page.image = { toPNG: () => data, getSize: () => ({ width: 1, height: 1 }) }; |
| 28 | await assert.rejects(captureScreenshot(page, null, 1, request, deps), /invalid_image/); |
| 29 | } |
| 30 | page.image = { toPNG: png, getSize: () => ({ width: 0, height: 0 }) }; |
| 31 | await assert.rejects(captureScreenshot(page, null, 1, request, deps), /dimensions/); |
| 32 | assert.equal(writes, 0); |
| 33 | page.image = { toPNG: png, getSize: () => ({ width: 1, height: 1 }) }; |
| 34 | assert.equal((await captureScreenshot(page, null, 1, request, deps)).width, 1); |
| 35 | assert.equal(writes, 1); |
| 36 | }); |
| 37 | |
| 38 | test("a changed owner cannot publish a captured image", async () => { |
| 39 | const page = new FakePage(1); |
| 40 | page.image = { toPNG: png, getSize: () => ({ width: 1, height: 1 }) }; |
| 41 | let checkpoints = 0; |
| 42 | await assert.rejects(captureScreenshot(page, null, 1, { ref: "", fullPage: false, directory: "/scratch" }, { |
| 43 | directoryExists: () => true, |
| 44 | verify: () => { if (++checkpoints === 2) throw new Error("revoked"); }, |
| 45 | writeFile: () => assert.fail("stale image was published"), |
| 46 | }), /revoked/); |
| 47 | }); |
| 48 | |
| 49 | test("CRC corruption and excessive full-page geometry fail before delivery", async t => { |
| 50 | t.mock.timers.enable({ apis: ["setTimeout"] }); |
| 51 | const corrupt = png(); corrupt[corrupt.length - 1] ^= 1; |
| 52 | assert.throws(() => validatePNG(corrupt), /invalid_image/); |
| 53 | const page = new FakePage(1); |
| 54 | page.debugger.respond = () => ({ cssContentSize: { width: 100000, height: 100000 } }); |
| 55 | await assert.rejects(captureScreenshot(page, null, 1, { ref: "", fullPage: true, directory: "/scratch" }, { |
| 56 | directoryExists: () => true, writeFile: () => assert.fail("oversize page was written"), |
| 57 | }), /pixel budget/); |
| 58 | t.mock.timers.tick(DEBUGGER_IDLE_MS); |
| 59 | assert.equal(page.debugger.isAttached(), false); |
| 60 | assert.equal(page.debugger.commands.some((entry) => entry.method === "Page.captureScreenshot"), false); |
| 61 | }); |
| 62 |