The per-tab account pin, the X-Siftlode-Account header and ?account= go: the session cookie alone says who is asking. localStorage keys lose their account suffix; the browser records whose state it holds (siftlode.owner), BootGate hands it to the account the server answered as, and a different account starts from a clean slate (the public /watch keys stay). The first boot moves the account's old per-account copies onto the plain keys and drops other accounts' copies. Until the claim, and after another tab claimed the browser for someone else, the account helpers read and write nothing. The account panel lists no other accounts: "Add another account" becomes "Sign in as someone else" (Google's account chooser; the pick replaces this account, a cancel keeps it), shown where Google sign-in is configured.
56 lines
2.3 KiB
TypeScript
56 lines
2.3 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" } }));
|
|
|
|
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");
|
|
});
|
|
});
|