Files
ng-eventually/packages/client/test/isolation.test.ts
T
Sylvain Duchesne 654cb90d99 feat(client): generic shared-wallet shim surface (docs/storeRegistry/isolation/accounts)
Port the shared-wallet shim mechanics from the Festipod app into the
generic @ng-eventually/client library — zero app-specific knowledge, the
consumer injects the domain (entity->scope mapping, connections, storage).

New namespaces exposed from src/index.ts:
- docs      docCreate/sparqlUpdate/sparqlQuery via the REAL injected `ng`
            (getConfig().ng), never the public makeNg proxy — the JS-over-
            iframe double proxy breaks doc_create postMessage marshaling
            (DataCloneError). Validated hard constraint.
- storeRegistry  generic (account,scope)->NURI resolver, createEntityDoc/
            listEntityDocs + per-scope index, sharedWalletShim in the
            private_store, cache. Consumer wiring injected via
            configureStoreRegistry({ getSession, normalizeUser }).
- isolation  pure applyIsolation (public=all / protected=owner+connections
            / private=owner); accessors + connection graph injected.
- accounts  AccountStore (localStorage-backed faux login, storage injected)
            + normalizeUsername. React wrapper intentionally NOT ported.

polyfill.ts gains configureStoreRegistry/getStoreRegistryDeps + resetConfig.
36/36 bun test, tsc --noEmit rc=0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 10:12:55 +02:00

69 lines
2.8 KiB
TypeScript

import { test, expect } from "bun:test";
import {
applyIsolation,
connectionsFromLinks,
visibleSet,
isVisible,
type IsolationAccessors,
} from "../src/isolation";
import type { Scope } from "../src/types";
// Generic item: owner + scope. ZERO domain — the accessors read these fields.
interface Item {
id: string;
owner: string;
scope: Scope;
}
const acc: IsolationAccessors<Item> = { ownerOf: (i) => i.owner, scopeOf: (i) => i.scope };
// alice —— bob (carol is unconnected)
const links = [{ a: "alice", b: "bob" }];
const conns = connectionsFromLinks(links);
test("connectionsFromLinks / visibleSet: self + direct connections, both directions", () => {
expect([...conns.neighbors("alice")].sort()).toEqual(["bob"]);
expect([...conns.neighbors("bob")].sort()).toEqual(["alice"]);
expect([...conns.neighbors("carol")]).toEqual([]);
expect([...visibleSet("alice", conns)].sort()).toEqual(["alice", "bob"]);
expect([...visibleSet("carol", conns)].sort()).toEqual(["carol"]);
});
test("isVisible matrix: public=all, protected=owner+connections, private=owner", () => {
const vis = visibleSet("alice", conns); // { alice, bob }
// public → everyone, regardless of owner
expect(isVisible("public", "carol", "alice", vis)).toBe(true);
// protected → owner must be in the visible set
expect(isVisible("protected", "bob", "alice", vis)).toBe(true);
expect(isVisible("protected", "carol", "alice", vis)).toBe(false);
// private → owner must be the current principal
expect(isVisible("private", "alice", "alice", vis)).toBe(true);
expect(isVisible("private", "bob", "alice", vis)).toBe(false);
});
test("applyIsolation narrows a mixed collection for the current principal", () => {
const items: Item[] = [
{ id: "e", owner: "carol", scope: "public" }, // public → kept for all
{ id: "p1", owner: "alice", scope: "protected" }, // own protected → kept
{ id: "p2", owner: "bob", scope: "protected" }, // connection's protected → kept
{ id: "p3", owner: "carol", scope: "protected" }, // stranger's protected → dropped
{ id: "s1", owner: "alice", scope: "private" }, // own private → kept
{ id: "s2", owner: "bob", scope: "private" }, // connection's private → dropped
];
const forAlice = applyIsolation(items, "alice", conns, acc).map((i) => i.id);
expect(forAlice).toEqual(["e", "p1", "p2", "s1"]);
const forCarol = applyIsolation(items, "carol", conns, acc).map((i) => i.id);
// carol sees: public + her own protected + her own private
expect(forCarol).toEqual(["e", "p3"]);
});
test("applyIsolation with empty principal passes everything through (hydration guard)", () => {
const items: Item[] = [
{ id: "s1", owner: "alice", scope: "private" },
{ id: "p1", owner: "bob", scope: "protected" },
];
expect(applyIsolation(items, "", conns, acc).map((i) => i.id)).toEqual(["s1", "p1"]);
});