import { expect } from "@std/expect"; import { describe, it } from "@std/testing/bdd"; import { COMMONFABRIC_TYPES } from "./commonfabric-test-types.ts"; import { callSchemas, parseModule } from "./transformed-ast.ts"; import { transformFiles, transformSource } from "./utils.ts"; const IMPORTS = `import { computed, handler, pattern, type Confidential, type RepresentsCurrentUser, type WriteAuthorizedBy, Writable } from "commonfabric"; interface Secret { a: string; b: string; } `; /** An emitted schema, read as a plain record. */ type Schema = Record; /** The properties of the schema of the first `computed()` capture in `source`. */ async function captureOf(source: string): Promise { const output = await transformSource(IMPORTS + source, { types: COMMONFABRIC_TYPES, typeCheck: true, }); const [capture] = callSchemas(parseModule(output), "lift"); return capture!.properties as Schema; } /** A pattern over `input` whose `computed()` returns `read`. */ function reading(input: string, read: string): string { return `export default pattern<{ ${input} }>((input) => ({ out: computed(() => ${read}), }));`; } /** A union of two labeled values that share the property `a`. */ const EITHER = `interface Other { a: string; c: number; } type Either = Confidential | Confidential;`; /** The schema of `Secret` with only `a` read, labeled with `ifc`. */ const A_ONLY = (ifc?: Schema) => ({ type: "object", properties: { a: { type: "string" } }, required: ["a"], ...(ifc && { ifc }), }); describe("narrowed capture labels", () => { it("keeps a label on a value read by a property chain", async () => { const capture = await captureOf(` export default pattern<{ secret: Confidential }>( ({ secret }) => ({ out: computed(() => secret.a) }), );`); expect(capture.secret).toEqual(A_ONLY({ confidentiality: ["top"] })); }); it("keeps a label on a value read by an optional chain", async () => { const capture = await captureOf(` export default pattern<{ maybe: Confidential | undefined }>( ({ maybe }) => ({ out: computed(() => maybe?.a ?? "") }), );`); expect(capture.maybe).toEqual(A_ONLY({ confidentiality: ["top"] })); }); it("keeps the label of an alias over a CFC alias", async () => { const capture = await captureOf(` type Sealed = Confidential; export default pattern<{ sealed: Sealed }>( ({ sealed }) => ({ out: computed(() => sealed.a) }), );`); expect(capture.sealed).toEqual(A_ONLY({ confidentiality: ["sealed"] })); }); it("keeps a label at the level of the value that carries it", async () => { const capture = await captureOf( reading( `nested: { inner: Confidential }`, "input.nested.inner.a", ), ); expect(capture.input).toEqual({ type: "object", properties: { nested: { type: "object", properties: { inner: A_ONLY({ confidentiality: ["deep"] }) }, required: ["inner"], }, }, required: ["nested"], }); }); it("keeps a label on a list read for its length", async () => { const capture = await captureOf(` export default pattern<{ list: Confidential }>( ({ list }) => ({ out: computed(() => list.length) }), );`); expect(capture.list).toEqual({ type: "object", properties: { length: { type: "number" } }, required: ["length"], ifc: { confidentiality: ["listed"] }, }); }); it("keeps a label on a value whose cell it reads", async () => { const capture = await captureOf(` interface Box { count: Writable; a: string; b: string; } export default pattern<{ box: Confidential }>( ({ box }) => ({ out: computed(() => box.count.get() + box.a) }), );`); expect(capture.box).toEqual({ type: "object", properties: { count: { type: "number", asCell: ["readonly"] }, a: { type: "string" }, }, required: ["count", "a"], ifc: { confidentiality: ["boxed"] }, }); }); it("keeps each element's label on an element read by index", async () => { const capture = await captureOf(` type Sealed = Confidential; export default pattern<{ rows: Sealed[] }>( ({ rows }) => ({ out: computed(() => rows?.[1]?.a ?? "") }), );`); expect(capture.rows).toEqual({ type: "array", items: A_ONLY({ confidentiality: ["sealed"] }), }); }); it("reads a label its declaration names by `typeof`", async () => { const capture = await captureOf(` const setSecret = handler<{ value: Secret }, { secret: Writable }>( ({ value }, { secret }) => secret.set(value), ); export default pattern<{ secret: WriteAuthorizedBy }>( ({ secret }) => ({ out: computed(() => secret.a) }), );`); expect(capture.secret).toMatchObject({ ifc: { writeAuthorizedBy: { __ctWriterIdentityOf: { path: ["setSecret"] } }, }, }); }); it("joins the confidentiality of each member a union may be", async () => { const capture = await captureOf(` interface Other { a: string; c: number; } type Either = Confidential | Confidential; export default pattern<{ either: Either }>( ({ either }) => ({ out: computed(() => either.a) }), );`); expect(capture.either).toEqual(A_ONLY({ confidentiality: ["x", "y"] })); }); it("keeps each element's label on a nullable element read by index", async () => { const capture = await captureOf(` export default pattern<{ rows: (Confidential | undefined)[] }>( ({ rows }) => ({ out: computed(() => rows[0]?.a ?? "") }), );`); expect(capture.rows).toEqual({ type: "array", items: { anyOf: [{ type: "undefined" }, A_ONLY()], ifc: { confidentiality: ["row"] }, }, }); }); describe("a policy its declaration names by `typeof`", () => { /** * The capture schema of `secret`, declared as `declaration` beside * `declarations`, read by `?.`. */ const policyCapture = async (declaration: string, declarations = "") => { const output = await transformFiles({ "/rules.ts": `import { type CfcExchangeRulesDeclaration, exchangeRule, exchangeRules, THIS_POLICY, } from "commonfabric/cfc"; export const neverRelease = exchangeRule({ appliesTo: THIS_POLICY, pre: { integrity: ["never"] }, post: { dropClause: true }, }); export const rules = exchangeRules([neverRelease]); export const releaseToReaders = exchangeRule({ appliesTo: THIS_POLICY, pre: { integrity: ["reader"] }, post: { dropClause: true }, }); export const releaseToWriters = exchangeRule({ appliesTo: THIS_POLICY, pre: { integrity: ["writer"] }, post: { dropClause: true }, }); export const readers: CfcExchangeRulesDeclaration = exchangeRules([ releaseToReaders, ]); export const writers: CfcExchangeRulesDeclaration = exchangeRules([ releaseToWriters, ]);`, "/test.tsx": `${IMPORTS} import { type PolicyOf } from "commonfabric/cfc"; import { readers, rules, writers } from "./rules.ts"; ${declarations} export default pattern<{ ${declaration} }>( ({ secret }) => ({ out: computed(() => secret?.a ?? "") }), );`, }, { types: COMMONFABRIC_TYPES, typeCheck: true }); const [capture] = callSchemas(parseModule(output["/test.tsx"]!), "lift"); return (capture!.properties as Schema).secret; }; const POLICY_LABEL = { ifc: { confidentiality: [{ policyRefKind: "module", __ctPolicyIdentityOf: { file: "/rules.ts", path: ["rules"] }, }], }, }; it("reads the policy through an optional chain", async () => { expect( await policyCapture( "secret: Confidential]>", ), ).toMatchObject(POLICY_LABEL); }); it("reads the policy of an optional property", async () => { expect( await policyCapture( "secret?: Confidential]>", ), ).toMatchObject(POLICY_LABEL); }); it("reads the policy a CFC alias over a union spells beside `null`", async () => { expect( await policyCapture( "secret: Confidential]> | null", "interface Other { a: string; c: number; }", ), ).toMatchObject(POLICY_LABEL); }); it("reads the policy of each of two CFC aliases over the same union", async () => { // `readers` and `writers` are declared with one type, so the checker // folds both labeled unions into the same members, and only their nodes // tell the policies apart. const labeled = (binding: string) => `Confidential]>`; const policy = (binding: string) => ({ policyRefKind: "module", __ctPolicyIdentityOf: { file: "/rules.ts", path: [binding] }, }); for ( const [first, second] of [["readers", "writers"], [ "writers", "readers", ]] ) { expect( await policyCapture( `secret: ${labeled(first!)} | ${labeled(second!)} | null`, "interface Other { a: string; c: number; }", ), ).toMatchObject({ ifc: { confidentiality: [policy(first!), policy(second!)] }, }); } }); for (const payload of ["Secret", "Secret | Other"]) { for (const nullable of [false, true]) { it(`reads both policies of ${payload}${nullable ? " beside null" : " without null"}`, async () => { const labeled = (binding: string) => `Confidential<${payload}, [PolicyOf]>`; const policy = (binding: string) => ({ policyRefKind: "module", __ctPolicyIdentityOf: { file: "/rules.ts", path: [binding] }, }); for ( const [first, second] of [["readers", "writers"], [ "writers", "readers", ]] ) { expect( await policyCapture( `secret: ${labeled(first!)} | ${labeled(second!)}${ nullable ? " | null" : "" }`, "interface Other { a: string; c: number; }", ), ).toMatchObject({ ifc: { confidentiality: [policy(first!), policy(second!)] }, }); } }); } } it("reads the policy of a value an alias of a nullable union names", async () => { expect( await policyCapture( "secret: MaybeSecret", "type MaybeSecret = Confidential]> | undefined;", ), ).toMatchObject(POLICY_LABEL); }); }); it("keeps a union's own label and each member's confidentiality", async () => { const capture = await captureOf(` ${EITHER} export default pattern<{ either: Confidential }>( ({ either }) => ({ out: computed(() => either.a) }), );`); expect(capture.either).toEqual( A_ONLY({ confidentiality: ["outer", "x", "y"] }), ); }); it("keeps each member's confidentiality under a union's empty label", async () => { const capture = await captureOf(` ${EITHER} export default pattern<{ either: Confidential }>( ({ either }) => ({ out: computed(() => either.a) }), );`); expect(capture.either).toEqual(A_ONLY({ confidentiality: ["x", "y"] })); }); it("keeps a label every member of a union declares alike", async () => { const capture = await captureOf(` interface Other { a: string; c: number; } type Either = | RepresentsCurrentUser> | RepresentsCurrentUser>; export default pattern<{ either: Either }>( ({ either }) => ({ out: computed(() => either.a) }), );`); expect((capture.either as Schema).ifc).toEqual({ confidentiality: ["x", "y"], addIntegrity: [{ kind: "represents-principal", subject: { __ctCurrentPrincipal: true }, }], }); }); it("adds no label of a property a rest binding does not read", async () => { const capture = await captureOf(` export default pattern<{ others: Confidential; x: { y: string; z: string }; }>(({ ...others }) => ({ out: computed(() => others.x.y) }));`); expect((capture.others as Schema).ifc).toBeUndefined(); }); it("adds no label to a value that carries none", async () => { const capture = await captureOf(` export default pattern<{ plain: Secret }>( ({ plain }) => ({ out: computed(() => plain.a) }), );`); expect(capture.plain).toEqual(A_ONLY()); }); });