Files
siftlode/frontend/src/lib/api/diagnostics.dom.test.ts
T
peter ec63056bd7 test(api): assert the export body's brand, not which realm built its Blob
`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.
2026-09-20 16:33:26 +02:00

61 lines
2.4 KiB
TypeScript

// @vitest-environment jsdom
//
// The zip export is the one api method that reads a RAW response instead of a parsed answer, so it
// carries contract of its own: the bytes, and the name the server picked. The composer's DOM test
// mocks this method wholesale, so without this the blob and the filename parse ship untested — and
// a broken header regex would silently save every report under the fallback name.
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("../errorDialog", () => ({ reportError: vi.fn() }));
vi.mock("../notifications", () => ({
notify: vi.fn(() => 1),
remove: vi.fn(),
getActiveToasts: vi.fn((): { id: number }[] => []),
}));
vi.mock("../../i18n", () => ({ default: { t: (k: string) => k, language: "en" } }));
vi.mock("../activeAccount", () => ({
activeAccountId: () => null,
ACTIVE_ACCOUNT_KEY: "k",
pinActiveAccount: () => {},
}));
import { debugSessionsApi } from "./diagnostics";
const zip = (disposition?: string) =>
new Response("PK", {
status: 200,
headers: {
"content-type": "application/zip",
...(disposition ? { "content-disposition": disposition } : {}),
},
});
beforeEach(() => vi.restoreAllMocks());
describe("debugExport", () => {
it("hands back the bytes and the name the server chose", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
zip('attachment; filename="siftlode-debug-20260919-1402-abc123.zip"'),
);
const out = await debugSessionsApi.debugExport("0123456789abcdef");
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.
// 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);
});
it("names the file itself when the answer does not", async () => {
// A proxy that strips the header must not leave the browser saving something called "export".
vi.spyOn(globalThis, "fetch").mockResolvedValue(zip());
const out = await debugSessionsApi.debugExport("0123456789abcdef");
expect(out.name).toBe("siftlode-debug-0123456789abcdef.zip");
});
});