import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; import type { PrincipalClaimKind } from "@commonfabric/api"; import { Identity } from "@commonfabric/identity"; import { waitForCellValue } from "@commonfabric/integration/wait-for-cell-value"; import type { MemorySpace, URI } from "@commonfabric/memory/interface"; import { currentPrincipal } from "../../src/builder/current-principal.ts"; import { pattern, popFrame, pushFrame } from "../../src/builder/pattern.ts"; import { principalOf, principalsOf } from "../../src/builder/principal-of.ts"; import type { JSONSchema } from "../../src/builder/types.ts"; import type { Cell } from "../../src/cell.ts"; import { UnknownCfcMetadataVersionError } from "../../src/cfc/metadata.ts"; import { Runtime } from "../../src/runtime.ts"; import { StorageManager } from "../../src/storage/cache.deno.ts"; import { setCfcImplementationIdentity } from "../../src/storage/extended-storage-transaction.ts"; import type { IExtendedStorageTransaction } from "../../src/storage/interface.ts"; import { isLinkResolutionProbe } from "../../src/storage/reactivity-log.ts"; import { SEED_ENVELOPE_SCHEMA_HASH, seedStoredEnvelope, writeSeedEnvelopeDoc, } from "../cfc-seed-envelope.ts"; import { createTrustedBuilder } from "../support/trusted-builder.ts"; const alice = await Identity.fromPassphrase("principal-of alice"); const bob = await Identity.fromPassphrase("principal-of bob"); const space = alice.did() as MemorySpace; const CURRENT_PRINCIPAL = { __ctCurrentPrincipal: true }; /** A stored label entry, as a seed writes it. */ type SeedEntry = { path: string[]; label: { confidentiality?: unknown[]; integrity?: unknown[] }; origin?: string; observes?: string; }; /** A claim of `kind` naming `subject`, in the form a runtime mints. */ const claim = (kind: PrincipalClaimKind, subject: string) => ({ kind, subject, }); /** An entry at `path` whose integrity is `atoms`. */ const claimsAt = (path: string[], ...atoms: unknown[]): SeedEntry => ({ path, label: { integrity: atoms }, origin: "declared", }); /** * A pattern whose handler and `computed()` each call `principalOf()` on the * cell they are given, reporting what it returned or the message it threw. */ const PROBE_PATTERN = [ "import {", " computed, handler, pattern, principalOf, Stream, Writable,", "} from 'commonfabric';", "type Kind = 'authored-by' | 'represents-principal';", "const probe = (", " target: Writable<{ name?: string }>,", " kind: Kind,", "): string => {", " try {", " return `returned ${principalOf(target, kind)}`;", " } catch (error) {", " return `threw ${(error as Error).message}`;", " }", "};", "const record = handler<", " { target: Writable<{ name?: string }>; kind: Kind },", " { seen: Writable }", ">((event, { seen }) => { seen.set(probe(event.target, event.kind)); });", "export default pattern<", " { seen: Writable; profile: Writable<{ name?: string }> },", " {", " seen: string;", " viaComputed: string;", " record: Stream;", " }", ">(({ seen, profile }) => ({", " seen,", " viaComputed: computed(() => probe(profile, 'represents-principal')),", " record: record({ seen }),", "}));", ].join("\n"); /** * A document whose `body` carries a principal claim of `kind` naming * `subject`, with the writer such a claim needs and a gesture its contract * needs to commit, so the only thing that can refuse the write is the check on * the claim itself. */ const claimedSchema = (kind: PrincipalClaimKind, subject: unknown) => ({ type: "object", properties: { body: { type: "string", ifc: { addIntegrity: [{ kind, subject }], writeAuthorizedBy: { __ctWriterIdentityOf: { file: "/writer.tsx", path: ["writeBody"] }, }, uiContract: { helper: "UiAction", action: "WriteBody", trustedPattern: "Writer", requiredEventIntegrity: ["Writer"], }, }, }, }, required: ["body"], }) as JSONSchema; describe("principalOf()", () => { let runtime: Runtime; let storage: ReturnType; let openTxs: IExtendedStorageTransaction[]; /** Opens a transaction the test aborts afterward. */ const edit = (): IExtendedStorageTransaction => { const tx = runtime.edit(); openTxs.push(tx); return tx; }; /** Runs `fn` in a `kind` frame over `tx`, and returns its result. */ const inFrame = ( tx: IExtendedStorageTransaction, kind: "handler" | "lift", fn: () => T, ): T => { const frame = pushFrame({ runtime, tx, space, frameKind: kind, ...(kind === "handler" ? { inHandler: true } : {}), }); try { return fn(); } finally { popFrame(frame); } }; /** Calls `principalOf(target, kind)` in a `frame` frame over `tx`. */ const callIn = ( tx: IExtendedStorageTransaction, target: unknown, kind: unknown, frame: "handler" | "lift" = "handler", ) => inFrame( tx, frame, () => principalOf(target, kind), ); /** Calls `principalsOf()` for `represents-principal` in a handler frame. */ const principalsIn = (tx: IExtendedStorageTransaction, target: unknown) => inFrame(tx, "handler", () => principalsOf(target, "represents-principal")); /** Returns the id of the document `cause` names in the test's space. */ const idOf = (cause: string): URI => runtime.getCell(space, cause).getAsNormalizedFullLink().id; /** * Stores `value` at the document `cause` names, with a label map of * `entries`, as the runtime would store it, and returns the document's cell. */ const seed = async ( cause: string, entries: SeedEntry[], value: unknown = { name: "Alice", bio: "hello" }, ): Promise> => { const tx = runtime.edit(); writeSeedEnvelopeDoc(tx, space); seedStoredEnvelope( tx, { space, scope: "space", id: idOf(cause), path: [] }, { value, ...(entries.length === 0 ? {} : { cfc: { version: 1, schemaHash: SEED_ENVELOPE_SCHEMA_HASH, labelMap: { version: 1, entries }, }, }), } as never, ); expect((await tx.commit().settled).error).toBeUndefined(); return runtime.getCell(space, cause); }; /** * Replaces the label map of the document `cause` names with one of * `entries`, leaving its value as it is. */ const relabel = async (cause: string, entries: SeedEntry[]) => { const tx = runtime.edit(); seedStoredEnvelope( tx, { space, scope: "space", id: idOf(cause), path: ["cfc"] }, { version: 1, schemaHash: SEED_ENVELOPE_SCHEMA_HASH, labelMap: { version: 1, entries }, } as never, ); expect((await tx.commit().settled).error).toBeUndefined(); }; /** * Writes a document at `cause` under `claimedSchema(kind, subject)` from a * handler, and returns the document's cell, the principal that handler's * `currentPrincipal()` returned, and the commit's error, if any. */ const writeClaimed = async ( cause: string, kind: PrincipalClaimKind, subject: unknown = CURRENT_PRINCIPAL, ) => { const tx = runtime.edit(); setCfcImplementationIdentity(tx, { kind: "verified", moduleIdentity: "principal-of-module", sourceFile: "/writer.tsx", bindingPath: ["writeBody"], }); const author = inFrame(tx, "handler", currentPrincipal); const cell = runtime.getCell( space, cause, claimedSchema(kind, subject), tx, ); cell.set({ body: "hello" }); const target = cell.getAsNormalizedFullLink(); tx.recordCfcWritePolicyInput({ kind: "trusted-event", target: { space: target.space, scope: target.scope, id: target.id, path: ["body"], }, eventId: "trusted-body-edit", provenance: { origin: "dom", trusted: true, ui: { pattern: "Writer", eventIntegrity: ["Writer"], uiContractDataset: { uiAction: "WriteBody" }, }, }, }); tx.prepareCfc(); const { error } = await tx.commit().settled; return { cell: runtime.getCell(space, cause), author, error }; }; beforeEach(() => { openTxs = []; storage = StorageManager.emulate({ as: alice }); runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager: storage, }); }); afterEach(async () => { for (const tx of openTxs) tx.abort(new Error("test-only")); await runtime.idle(); await runtime.dispose(); await storage.close(); }); describe("reading a label", () => { it("returns the DID a `represents-principal` claim at the root attests", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); }); it("returns the DID the claims on a profile's top-level fields attest", async () => { const profile = await seed("profile", [ claimsAt(["name"], claim("represents-principal", bob.did())), claimsAt(["bio"], claim("represents-principal", bob.did())), ]); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); }); it("returns the author an `authored-by` claim attests, and nothing for the other kind", async () => { const record = await seed("record", [ claimsAt(["name"], claim("authored-by", bob.did())), ]); expect(callIn(edit(), record, "authored-by")).toBe(bob.did()); expect(callIn(edit(), record, "represents-principal")).toBeUndefined(); }); it("returns the same DID in a reactive computation's frame", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); expect(callIn(edit(), profile, "represents-principal", "lift")).toBe( bob.did(), ); }); it("returns the DID the document a link leads to attests", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); const holder = runtime.getCell(space, "holder"); const tx = runtime.edit(); holder.withTx(tx).set(profile); expect((await tx.commit().settled).error).toBeUndefined(); expect(callIn(edit(), holder, "represents-principal")).toBe(bob.did()); }); it("returns the DID the document a link leads to attests, for the link passed as its cell's reactive proxy", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); const holder = runtime.getCell(space, "holder"); const tx = runtime.edit(); holder.withTx(tx).set(profile); expect((await tx.commit().settled).error).toBeUndefined(); expect( callIn(edit(), holder.getAsReactiveProxy(), "represents-principal"), ).toBe(bob.did()); }); it("returns `undefined` for a document whose label attests no principal", async () => { const unlabeled = await seed("unlabeled", []); const other = await seed("other", [ { path: ["name"], label: { confidentiality: ["secret"] } }, ]); expect(callIn(edit(), unlabeled, "represents-principal")) .toBeUndefined(); expect(callIn(edit(), other, "represents-principal")).toBeUndefined(); }); it("returns `undefined` for a label attesting two principals", async () => { // No name is written, so every top-level field counts. const profile = await seed("profile", [ claimsAt(["name"], claim("represents-principal", alice.did())), claimsAt(["bio"], claim("represents-principal", bob.did())), ], { bio: "hello" }); expect(callIn(edit(), profile, "represents-principal")).toBeUndefined(); }); it("skips a claim in any form but an object of the kind with a `did:` subject, which refuses nothing", async () => { const forms = [ `represents-principal:${bob.did()}`, { kind: "represents-principal", subject: ` ${bob.did()}` }, { kind: "represents-principal", subject: bob.did(), scope: "x" }, { kind: "represents-principal", subject: "not-a-did" }, ]; for (const [index, form] of forms.entries()) { const profile = await seed(`profile-${index}`, [ claimsAt([], claim("represents-principal", bob.did())), claimsAt(["name"], form), ]); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); } }); it("returns the principal a deeper or link-carried claim names, for a value whose own fields name no one", async () => { // A value with no written name is read from every entry it stores, // deeper ones and ones a link carried included. Its `name` here is not // a string, so nothing is written to it. const deep = await seed("deep", [ claimsAt(["name", "first"], claim("represents-principal", bob.did())), ], { name: { first: "Bob" } }); const carried = await seed("carried", [{ ...claimsAt(["friend"], claim("represents-principal", bob.did())), observes: "followRef", }], { friend: "linked" }); expect(callIn(edit(), deep, "represents-principal")).toBe(bob.did()); expect(callIn(edit(), carried, "represents-principal")).toBe(bob.did()); }); it("returns `undefined` for a value with no written name whose own field and a deeper claim name different principals", async () => { const profile = await seed("profile", [ claimsAt(["bio"], claim("represents-principal", bob.did())), claimsAt( ["elements", "0", "cell"], claim("represents-principal", alice.did()), ), ], { bio: "hello" }); expect(callIn(edit(), profile, "represents-principal")).toBeUndefined(); }); it("returns the principal a link carried onto a written name names", async () => { const profile = await seed("profile", [ { ...claimsAt(["name"], claim("represents-principal", bob.did())), observes: "followRef", }, claimsAt(["bio"], claim("represents-principal", alice.did())), ], { name: "Bob", bio: "hello" }); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); }); it("throws for a label stored in a form this build cannot read", async () => { // The same document reads as attesting bob until its label is replaced // with one of an unknown version, so the throw is the label's doing. const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); const tx = runtime.edit(); seedStoredEnvelope( tx, { space, scope: "space", id: idOf("profile"), path: ["cfc"] }, { version: 999, schemaHash: SEED_ENVELOPE_SCHEMA_HASH, labelMap: { version: 1, entries: [claimsAt([], claim("represents-principal", bob.did()))], }, } as never, ); expect((await tx.commit().settled).error).toBeUndefined(); expect(() => callIn(edit(), profile, "represents-principal")).toThrow( UnknownCfcMetadataVersionError, ); }); it("returns `undefined` for a `target` passed as `undefined`", () => { expect(callIn(edit(), undefined, "represents-principal")) .toBeUndefined(); }); }); describe("a profile's written name", () => { // A profile whose owner wrote a name says whose it is on the root and that // field alone. Its other fields, still at their defaults, can carry the // principal of whichever runtime set them up: here alice's, beside or in // place of bob's. /** The label of bob's profile, its defaults also stamped by alice. */ const stamped = (): SeedEntry[] => [ claimsAt(["name"], claim("represents-principal", bob.did())), claimsAt( ["avatar"], claim("represents-principal", bob.did()), claim("represents-principal", alice.did()), ), claimsAt( ["bio"], claim("represents-principal", bob.did()), claim("represents-principal", alice.did()), ), claimsAt(["inbox"], claim("represents-principal", alice.did())), ]; it("returns the owner of a stamped profile whose owner wrote a name", async () => { const profile = await seed("profile", stamped(), { name: "Bob" }); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); expect(principalsIn(edit(), profile)).toEqual([bob.did()]); }); it("returns `undefined` for a stamped profile whose name is unwritten", async () => { // The control for the case above: the same label, with nothing written // to the name. for ( const [cause, value] of [ ["absent", {}], ["empty", { name: "" }], ["blank", { name: " " }], ] as const ) { const profile = await seed(cause, stamped(), value); expect(callIn(edit(), profile, "represents-principal")) .toBeUndefined(); expect(principalsIn(edit(), profile)).toEqual([ bob.did(), alice.did(), ]); } }); it("returns the principal a written name attests, though another field attests only someone else", async () => { const profile = await seed("profile", [ claimsAt(["name"], claim("represents-principal", bob.did())), claimsAt(["bio"], claim("represents-principal", alice.did())), ], { name: "Bob", bio: "hello" }); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); }); it("returns the principal the root and a written name agree on", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), claimsAt(["name"], claim("represents-principal", bob.did())), claimsAt(["bio"], claim("represents-principal", alice.did())), ], { name: "Bob" }); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); }); it("returns `undefined` for a written name whose own label attests two principals", async () => { const profile = await seed("profile", [ claimsAt( ["name"], claim("represents-principal", bob.did()), claim("represents-principal", alice.did()), ), claimsAt(["bio"], claim("represents-principal", bob.did())), ], { name: "Bob" }); expect(callIn(edit(), profile, "represents-principal")).toBeUndefined(); }); it("returns `undefined` for an unwritten name carrying another runtime's principal alone, the owner's being elsewhere", async () => { const profile = await seed("profile", [ claimsAt(["name"], claim("represents-principal", alice.did())), claimsAt(["avatar"], claim("represents-principal", bob.did())), claimsAt(["bio"], claim("represents-principal", bob.did())), ], { name: "" }); expect(callIn(edit(), profile, "represents-principal")).toBeUndefined(); }); it("returns the one principal a profile with no written name attests across its fields", async () => { const profile = await seed("profile", [ claimsAt(["name"], claim("represents-principal", bob.did())), claimsAt(["bio"], claim("represents-principal", bob.did())), ], { name: "" }); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); }); it("returns the name a link holds, followed to where it leads", async () => { const name = runtime.getCell(space, "linked name"); const tx = runtime.edit(); name.withTx(tx).set("Bob"); expect((await tx.commit().settled).error).toBeUndefined(); const profile = await seed("profile", stamped(), { name: name.getAsLink(), }); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); }); it("skips a string-form claim on another field, whether or not the name is written", async () => { const profile = await seed("profile", [ claimsAt(["name"], claim("represents-principal", bob.did())), claimsAt(["bio"], `represents-principal:${alice.did()}`), ], { name: "Bob" }); expect(callIn(edit(), profile, "represents-principal")).toBe(bob.did()); expect(principalsIn(edit(), profile)).toEqual([bob.did()]); }); it("returns an `authored-by` claim on any field, whether or not the name is written", async () => { const record = await seed("record", [ claimsAt(["bio"], claim("authored-by", bob.did())), ], { name: "Bob", bio: "hello" }); expect(callIn(edit(), record, "authored-by")).toBe(bob.did()); }); }); describe("claims the runtime minted", () => { it("returns the author of a record a handler wrote with `AuthoredByCurrentUser`, which is that handler's `currentPrincipal()`", async () => { const { cell, author, error } = await writeClaimed( "authored", "authored-by", ); expect(error).toBeUndefined(); expect(author).toBe(alice.did()); expect(callIn(edit(), cell, "authored-by")).toBe(author); }); it("returns the principal a profile written with `RepresentsCurrentUser` represents", async () => { const { cell, author, error } = await writeClaimed( "represented", "represents-principal", ); expect(error).toBeUndefined(); expect(callIn(edit(), cell, "represents-principal")).toBe(author); }); it("returns `undefined` for a record whose pattern-written claim names a DID, since the write is refused", async () => { // The same write with the runtime's placeholder commits, above; only // the literal subject differs. const { cell, error } = await writeClaimed( "forged", "authored-by", bob.did(), ); expect(error).toBeDefined(); expect(callIn(edit(), cell, "authored-by")).toBeUndefined(); }); it("returns a DID that a pattern cannot write back into a label as a claim's subject", async () => { const { cell } = await writeClaimed("original", "authored-by"); const returned = callIn(edit(), cell, "authored-by"); expect(returned).toBe(alice.did()); const copy = await writeClaimed("copy", "authored-by", returned); expect(copy.error).toBeDefined(); expect(callIn(edit(), copy.cell, "authored-by")).toBeUndefined(); }); }); describe("what it reads", () => { it("reads the label, and of the value only its `name`, through the calling code's transaction", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); const id = idOf("profile"); for (const frame of ["handler", "lift"] as const) { const tx = edit(); expect(callIn(tx, profile, "represents-principal", frame)).toBe( bob.did(), ); const reads = [...tx.getReadActivities!()].filter((read) => read.id === id ); expect(reads).toContainEqual( expect.objectContaining({ path: ["cfc"] }), ); // Resolving the cell, and its `name`, probes whether each is a link. // Beyond that only the name is read, to see whether it was written. expect( reads.filter((read) => read.path[0] === "value" && !isLinkResolutionProbe(read.meta) ).map((read) => read.path), ).toEqual([["value", "name"]]); } }); it("reads nothing of the value for an `authored-by` claim", async () => { const record = await seed("record", [ claimsAt([], claim("authored-by", bob.did())), ]); const id = idOf("record"); const tx = edit(); expect(callIn(tx, record, "authored-by")).toBe(bob.did()); expect( [...tx.getReadActivities!()].filter((read) => read.id === id && read.path[0] === "value" && !isLinkResolutionProbe(read.meta) ), ).toEqual([]); }); it("runs a `lift()` that called it again when the label alone changes", async () => { // A second lift holds the same cell and reads nothing of it but the // link probe, so it shows that the change reaches only a computation // that read the label. await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); const runs = { principal: 0, held: 0 }; const argumentSchema = { type: "object", properties: { target: { type: "unknown", asCell: ["cell"] } }, } as const satisfies JSONSchema; const { lift, pattern: trustedPattern } = createTrustedBuilder(runtime).commonfabric; const principal = lift( (input: { target?: unknown }) => { runs.principal++; return principalOf( input.target as Cell, "represents-principal", ) ?? "none"; }, argumentSchema, { type: "string" }, ); const held = lift( (input: { target?: unknown }) => { runs.held++; (input.target as Cell).resolveAsCell(); return "held"; }, argumentSchema, { type: "string" }, ); const probe = trustedPattern( ({ target }) => ({ principal: principal({ target }), held: held({ target }), }), argumentSchema, { type: "object", properties: { principal: { type: "string" }, held: { type: "string" }, }, }, ); const tx = runtime.edit(); const result = runtime.run( tx, probe, { target: runtime.getCell(space, "profile") }, runtime.getCell(space, "principal-of-lift", undefined, tx), ) as Cell<{ principal?: string; held?: string }>; expect((await tx.commit().settled).error).toBeUndefined(); const cancel = result.sink(() => {}); try { await waitForCellValue( runtime, result.key("principal"), (value) => value === bob.did(), { stuckLabel: "the lift to return bob's DID" }, ); expect(runs).toEqual({ principal: 1, held: 1 }); await relabel("profile", [ claimsAt([], claim("represents-principal", alice.did())), ]); await waitForCellValue( runtime, result.key("principal"), (value) => value === alice.did(), { stuckLabel: "the lift to follow the label to alice's DID" }, ); expect(runs).toEqual({ principal: 2, held: 1 }); } finally { cancel(); } }); it("runs a `lift()` that called it again when the name is written", async () => { // The profile's fields name two principals, so it names neither until // the name its owner writes says whose it is. The name lives in a // document of its own, as a profile's does, which the profile links. const name = runtime.getCell(space, "lift name"); const blank = runtime.edit(); name.withTx(blank).set(""); expect((await blank.commit().settled).error).toBeUndefined(); await seed("profile", [ claimsAt(["name"], claim("represents-principal", bob.did())), claimsAt(["avatar"], claim("represents-principal", alice.did())), ], { name: name.getAsLink() }); const argumentSchema = { type: "object", properties: { target: { type: "unknown", asCell: ["cell"] } }, } as const satisfies JSONSchema; const { lift, pattern: trustedPattern } = createTrustedBuilder(runtime).commonfabric; const principal = lift( (input: { target?: unknown }) => principalOf(input.target as Cell, "represents-principal") ?? "none", argumentSchema, { type: "string" }, ); const probe = trustedPattern( ({ target }) => ({ principal: principal({ target }) }), argumentSchema, { type: "object", properties: { principal: { type: "string" } } }, ); const tx = runtime.edit(); const result = runtime.run( tx, probe, { target: runtime.getCell(space, "profile") }, runtime.getCell(space, "principal-of-name-lift", undefined, tx), ) as Cell<{ principal?: string }>; expect((await tx.commit().settled).error).toBeUndefined(); const cancel = result.sink(() => {}); try { await waitForCellValue( runtime, result.key("principal"), (value) => value === "none", { stuckLabel: "the lift to return no principal" }, ); const write = runtime.edit(); name.withTx(write).set("Bob"); expect((await write.commit().settled).error).toBeUndefined(); await waitForCellValue( runtime, result.key("principal"), (value) => value === bob.did(), { stuckLabel: "the lift to follow the written name to bob's DID" }, ); } finally { cancel(); } }); it("records no label-metadata observation, since a claim's subject is public", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); const tx = edit(); expect(callIn(tx, profile, "represents-principal")).toBe(bob.did()); expect(tx.getCfcState().labelMetadataObservations).toEqual([]); }); }); describe("a load in flight", () => { /** * Makes the storage manager report a load in flight for every document, * which is the state a first read of a document leaves while the load it * started has not landed. */ const loading = () => { Object.assign(storage, { pendingLoadGeneration: () => 1 }); }; it("withdraws the handler that reads its label while its load is in flight", () => { loading(); const tx = edit(); const target = runtime.getCell(space, "still-loading"); expect(callIn(tx, target, "represents-principal")).toBeUndefined(); expect(tx.dispatchedHandlerNotRun?.reason).toContain( target.getAsNormalizedFullLink().id, ); }); it("withdraws the handler that reads the label of a scoped document loading for the identity its transaction demands", () => { // Only Bob's instance of the document is loading. The runtime acts for // Alice, and the transaction demands Bob, as a served run demands its // actor. Object.assign(storage, { pendingLoadGeneration: (key: string) => key.includes(encodeURIComponent(bob.did())) ? 1 : undefined, }); const tx = edit(); tx.tx.scopeKeyIdentity = { principal: bob.did() }; const target = runtime.getCell( space, "still-loading-for-bob", undefined, undefined, "user", ); expect(callIn(tx, target, "represents-principal")).toBeUndefined(); expect(tx.dispatchedHandlerNotRun?.reason).toContain( target.getAsNormalizedFullLink().id, ); }); it("leaves a reactive computation that reads its label while its load is in flight to run", () => { loading(); const tx = edit(); const target = runtime.getCell(space, "still-loading"); expect(callIn(tx, target, "represents-principal", "lift")) .toBeUndefined(); expect(tx.dispatchedHandlerNotRun).toBeUndefined(); }); it("leaves the handler that reads its label to run, returning `undefined`, when no load is in flight", () => { const tx = edit(); const target = runtime.getCell(space, "never-written"); expect(callIn(tx, target, "represents-principal")).toBeUndefined(); expect(tx.dispatchedHandlerNotRun).toBeUndefined(); }); it("leaves the handler that reads the label of a document it holds to run, while a load is in flight", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); loading(); const tx = edit(); expect(callIn(tx, profile, "represents-principal")).toBe(bob.did()); expect(tx.dispatchedHandlerNotRun).toBeUndefined(); }); }); describe("in a compiled pattern", () => { /** Compiles and runs `PROBE_PATTERN` over `profile`. */ const runProbe = async (profile: Cell) => { const compiled = await runtime.patternManager.compilePattern({ main: "/main.tsx", files: [{ name: "/main.tsx", contents: PROBE_PATTERN }], }, { space }); const argument = runtime.getCell<{ seen: string; profile: unknown }>( space, "principal-of-probe-argument", ); const result = runtime.getCell<{ seen: string; viaComputed: string; record: unknown; }>(space, "principal-of-probe-result", compiled.resultSchema); { const tx = runtime.edit(); argument.withTx(tx).set({ seen: "no event yet", profile }); expect((await tx.commit().settled).error).toBeUndefined(); } { const tx = runtime.edit(); runtime.run(tx, compiled, argument, result); expect((await tx.commit().settled).error).toBeUndefined(); } const cancel = result.sink(() => {}); await runtime.idle(); return { result, cancel }; }; it("returns the principal to a handler, on a cell its event names", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); const record = await seed("record", [ claimsAt(["name"], claim("authored-by", alice.did())), ]); const { result, cancel } = await runProbe(profile); try { result.key("record").send({ target: record, kind: "authored-by", } as never); await runtime.idle(); expect(result.key("seen").get()).toBe(`returned ${alice.did()}`); } finally { cancel(); } }); it("returns the principal to a `computed()`", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); const { result, cancel } = await runProbe(profile); try { await waitForCellValue( runtime, result.key("viaComputed"), (value) => value === `returned ${bob.did()}`, { stuckLabel: "the computed to return bob's DID" }, ); } finally { cancel(); } }); }); describe("where it can be called", () => { it("throws outside a handler or a reactive computation", () => { expect(() => principalOf(undefined, "represents-principal")).toThrow( "can only be called from a handler or a reactive computation", ); }); it("throws in a pattern body, even one built inside a handler", () => { const tx = edit(); inFrame(tx, "handler", () => { expect(() => pattern(() => { principalOf(undefined, "represents-principal"); return {}; }) ).toThrow( "can only be called from a handler or a reactive computation", ); }); }); it("throws for a `kind` that is not a principal claim kind", () => { expect(() => callIn(edit(), undefined, "owner")).toThrow( "takes a `kind` of `authored-by` or `represents-principal`", ); }); it("throws for a `target` that is not a cell", () => { expect(() => callIn(edit(), { name: "Bob" }, "authored-by")).toThrow( "takes a cell as its target", ); }); }); describe('with `label` of `"written"`', () => { /** Calls `principalOf(target, kind, options)` in a handler frame. */ const callAt = (target: unknown, kind: unknown, options: unknown) => inFrame(edit(), "handler", () => principalOf(target, kind, options)); /** * Stores a document under `cause` whose `by` field links a profile that * attests bob, with a label map of `entries`, and returns its cell. */ const holding = async (cause: string, entries: SeedEntry[]) => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); return await seed(cause, entries, { by: profile.getAsLink(), title: "Held", }); }; /** The copy of the linked profile's claim a link carries at `by`. */ const carried: SeedEntry = { path: ["by"], label: { integrity: [claim("represents-principal", bob.did())] }, origin: "link", }; it("returns the DID attested on a field holding a link, where the default returns the linked document's", async () => { const holder = await holding("holder", [ claimsAt(["by"], claim("represents-principal", alice.did())), carried, ]); const field = holder.key("by"); expect(callIn(edit(), field, "represents-principal")).toBe(bob.did()); expect(callAt(field, "represents-principal", { label: "resolved" })) .toBe(bob.did()); expect(callAt(field, "represents-principal", { label: "written" })) .toBe(alice.did()); expect( inFrame( edit(), "handler", () => principalsOf(field, "represents-principal", { label: "written" }), ), ).toEqual([alice.did()]); }); it("does not count the claim a link carries from the document it leads to", async () => { const holder = await holding("holder", [carried]); const field = holder.key("by"); expect(callAt(field, "represents-principal", { label: "written" })) .toBeUndefined(); expect( inFrame( edit(), "handler", () => principalsOf(field, "represents-principal", { label: "written" }), ), ).toEqual([]); }); it("follows the links on the way to the field", async () => { const holder = await holding("holder", [ claimsAt(["by"], claim("represents-principal", alice.did())), ]); const via = runtime.getCell<{ by: unknown }>(space, "via"); const tx = runtime.edit(); via.withTx(tx).set(holder as never); expect((await tx.commit().settled).error).toBeUndefined(); expect( callAt(via.key("by"), "represents-principal", { label: "written" }), ).toBe(alice.did()); }); it("follows a redirect stored in the field, as a write to it would", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); const holder = await seed("redirecting", [ claimsAt(["by"], claim("represents-principal", alice.did())), ], { by: profile.getAsWriteRedirectLink() }); expect( callAt(holder.key("by"), "represents-principal", { label: "written" }), ).toBe(bob.did()); }); it("reads a field holding no link as the default does", async () => { const record = await seed("record", [ claimsAt(["name"], claim("authored-by", bob.did())), ]); const field = record.key("name" as never); expect(callAt(field, "authored-by", { label: "written" })).toBe( bob.did(), ); expect(callIn(edit(), field, "authored-by")).toBe(bob.did()); }); it("throws for `options` in another form", async () => { const profile = await seed("profile", [ claimsAt([], claim("represents-principal", bob.did())), ]); for (const options of [null, "no", [], { label: "field" }]) { expect(() => callAt(profile, "represents-principal", options)) .toThrow('takes `options` of `{ label?: "written" | "resolved" }`'); } }); }); describe("principalsOf()", () => { /** Calls `principalsOf(target, kind)` in a handler frame over `tx`. */ const callAllIn = ( tx: IExtendedStorageTransaction, target: unknown, kind: unknown, ) => inFrame(tx, "handler", () => principalsOf(target, kind)); it("returns no principals for a document whose label attests none", async () => { const unlabeled = await seed("unlabeled", []); expect(callAllIn(edit(), unlabeled, "authored-by")).toEqual([]); }); it("returns the one principal a claim attests", async () => { const record = await seed("record", [ claimsAt(["name"], claim("authored-by", bob.did())), ]); expect(callAllIn(edit(), record, "authored-by")).toEqual([bob.did()]); }); it("returns every principal a label attests, in the order they first appear, where `principalOf()` returns `undefined`", async () => { const record = await seed("record", [ claimsAt(["name"], claim("authored-by", bob.did())), claimsAt(["bio"], claim("authored-by", alice.did())), claimsAt([], claim("authored-by", bob.did())), ]); expect(callAllIn(edit(), record, "authored-by")).toEqual([ bob.did(), alice.did(), ]); expect(callIn(edit(), record, "authored-by")).toBeUndefined(); }); it("returns only the principals a key's own field attests, leaving out another field's", async () => { await seed("panel", [ claimsAt(["addedBy"], claim("authored-by", alice.did())), claimsAt(["titleOverride"], claim("authored-by", bob.did())), ], { addedBy: alice.did(), titleOverride: "Bob's title" }); const panel = runtime.getCell< { addedBy: string; titleOverride: string } >(space, "panel"); expect(callAllIn(edit(), panel.key("addedBy"), "authored-by")).toEqual([ alice.did(), ]); expect(callAllIn(edit(), panel, "authored-by")).toEqual([ alice.did(), bob.did(), ]); }); it("returns `undefined` for a label holding a claim in any form but the one a runtime mints", async () => { const record = await seed("record", [ claimsAt([], claim("authored-by", bob.did())), claimsAt(["name"], `authored-by:${bob.did()}`), ]); expect(callAllIn(edit(), record, "authored-by")).toBeUndefined(); }); it("returns `undefined` for a `target` passed as `undefined`", () => { expect(callAllIn(edit(), undefined, "authored-by")).toBeUndefined(); }); it("throws outside a handler or a reactive computation, naming itself", () => { expect(() => principalsOf(undefined, "authored-by")).toThrow( "`principalsOf(target, kind)` can only be called from a handler or a reactive computation", ); }); }); });