From ec63056bd730233e283b01a085550a1ca582e456 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 20 Sep 2026 16:33:26 +0200 Subject: [PATCH] test(api): assert the export body's brand, not which realm built its Blob MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `expect(x).toBeInstanceOf(Blob)` asks which realm constructed the body, not whether there is one. Under jsdom the ambient `Blob` is jsdom's class, while `Response.blob()` hands back node's own on Node 22 and jsdom's on Node 24 — so both assertions passed locally and failed on the server gate, for a reason that says nothing about the code under test. `.text()` is the mirror image: jsdom's Blob does not implement it, so reading the bytes back is green on 22 and red on 24. Both now assert the brand (`[object Blob]`, agreed by both realms) plus size and type. Mutation-checked in node:22-bookworm: dropping `{ raw: true }` from `debugExport` still turns the test red, so the repaired assertion still catches the defect it was written for. core.test.ts also stops building its Response from a jsdom `Blob`: on Node 22 undici does not recognise it as a body and stringifies it, so the fixture's body was the 13 bytes of "[object Blob]" rather than the zip header. A string body is what the sibling test already used. --- frontend/src/lib/api/core.test.ts | 13 +++++++++++-- frontend/src/lib/api/diagnostics.dom.test.ts | 5 ++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/api/core.test.ts b/frontend/src/lib/api/core.test.ts index c71b2b01..2f23ca1f 100644 --- a/frontend/src/lib/api/core.test.ts +++ b/frontend/src/lib/api/core.test.ts @@ -221,7 +221,7 @@ describe("req — a file, not a parsed answer", () => { // A zip. Without the `raw` branch this falls into `r.json()`, throws on the first byte, and the // download dies as an opaque failure — which no lane would see, because the only consumer test // mocks the api method wholesale. - const zip = new Response(new Blob(["PK"]), { + const zip = new Response("PK", { status: 200, headers: { "content-type": "application/zip", @@ -234,7 +234,16 @@ describe("req — a file, not a parsed answer", () => { expect(r).toBe(zip); expect(r.headers.get("content-disposition")).toContain("siftlode-debug-260919.zip"); - expect(await r.blob()).toBeInstanceOf(Blob); // the body is still there to be read + // NOT `toBeInstanceOf(Blob)`: that asserts which realm built the body, not that there is one. + // Under jsdom the ambient `Blob` is jsdom's class, while `Response.blob()` hands back node's own + // on Node 22 and jsdom's on Node 24 — so the same assertion passed here and failed on the server + // gate (measured 2026-09-20). Nor `.text()`, for the mirror-image reason: jsdom's Blob has no + // such method, so reading the bytes back is green on 22 and red on 24. The brand and the size + // are the two things both realms agree on, and they are what this test means. + const body = await r.blob(); + expect(Object.prototype.toString.call(body)).toBe("[object Blob]"); + expect(body.size).toBe(4); // the body is still there to be read, all four of its bytes + expect(body.type).toBe("application/zip"); }); it("still refuses before the body is touched, so a refusal is shown and not saved", async () => { diff --git a/frontend/src/lib/api/diagnostics.dom.test.ts b/frontend/src/lib/api/diagnostics.dom.test.ts index 68ba8010..af46e429 100644 --- a/frontend/src/lib/api/diagnostics.dom.test.ts +++ b/frontend/src/lib/api/diagnostics.dom.test.ts @@ -42,7 +42,10 @@ describe("debugExport", () => { expect(out.name).toBe("siftlode-debug-20260919-1402-abc123.zip"); // The body is read as bytes, not parsed: `r.json()` on a zip would throw on its first one. - expect(out.blob).toBeInstanceOf(Blob); + // Brand, not `toBeInstanceOf(Blob)` — that asks which realm built the body: under jsdom the + // ambient `Blob` is jsdom's class, while `Response.blob()` returns node's own on Node 22 and + // jsdom's on Node 24, so the instance check passed locally and failed on the server gate. + expect(Object.prototype.toString.call(out.blob)).toBe("[object Blob]"); expect(out.blob.size).toBe(4); });