/** * The access gate's identity resolution. * * This is the piece whose failure is SILENT: get the order wrong and the broker iframe * reads an empty identity, provisions a second virtual user, and the returning user * lands in an empty space with no error anywhere. So the order is pinned, not trusted. */ import { test, expect, afterEach } from "bun:test"; import { configure, resetConfig, setCurrentUser, getCurrentUser } from "../src/polyfill"; import { ensureIdentity } from "../src/shared-wallet/access-gate"; const KEY = "ng-eventually:identity"; /** A localStorage double — the real one is absent in `bun test`. */ function fakeStorage(initial: Record = {}) { const map = new Map(Object.entries(initial)); return { getItem: (k: string) => map.get(k) ?? null, setItem: (k: string, v: string) => void map.set(k, v), removeItem: (k: string) => void map.delete(k), get size() { return map.size; }, }; } /** Put the page in a given URL + storage state, as the browser would. */ function inPage(search: string, storage: ReturnType) { (globalThis as any).location = { search, href: "https://app.example" + search }; (globalThis as any).localStorage = storage; (globalThis as any).history = { replaceState: () => {} }; } afterEach(() => { setCurrentUser(null); resetConfig(); delete (globalThis as any).location; delete (globalThis as any).localStorage; delete (globalThis as any).history; }); function configured() { configure({ ng: {} as never, useShape: (() => {}) as never, sharedWallet: { fileUrl: "/w.ngw", password: "pw" }, }); } test("an identity already set is left alone — the gate never re-asks", async () => { configured(); inPage("", fakeStorage()); setCurrentUser("alice"); await ensureIdentity(); expect(getCurrentUser()).toBe("alice"); }); test("the URL parameter WINS over storage — it is the only thing that crosses the frontier", async () => { // The top-level page and the broker iframe have separate localStorage partitions, so a // value written on one side is not the value the other reads. The URL survives the // round-trip; storage does not. If storage won here, a user entering a second // identifier would keep being sent back to the first one's space. configured(); inPage("?ng-id=fromurl", fakeStorage({ [KEY]: "fromstorage" })); await ensureIdentity(); expect(getCurrentUser()).toBe("fromurl"); }); test("the URL parameter is copied into THIS partition, so a plain reload still knows", async () => { configured(); const storage = fakeStorage(); inPage("?ng-id=carol", storage); await ensureIdentity(); expect(storage.getItem(KEY)).toBe("carol"); }); test("with no parameter, storage answers — a reload does not re-ask", async () => { configured(); inPage("", fakeStorage({ [KEY]: "dana" })); await ensureIdentity(); expect(getCurrentUser()).toBe("dana"); }); test("nothing known and no DOM to ask on → it refuses loudly", async () => { // Continuing silently would provision an anonymous virtual space, which is the failure // this module exists to prevent. The error names what the caller must do. configured(); inPage("", fakeStorage()); await expect(ensureIdentity()).rejects.toThrow(/no DOM to ask on/i); }); test("no shared wallet configured → it refuses, rather than inventing a space", async () => { configure({ ng: {} as never, useShape: (() => {}) as never }); inPage("", fakeStorage()); await expect(ensureIdentity()).rejects.toThrow(/no shared wallet configured/i); });