import assert from "node:assert/strict"; import { test } from "node:test"; import { compileCapabilitySource } from "../src/capability-language/index.js"; import { capabilityFixturePath, capabilityFixtureSource, fixtureId, } from "./fixtures/capability-model.js"; test("ANTLR parses and validates a complete capability workspace", () => { const result = compileCapabilitySource( capabilityFixtureSource, capabilityFixturePath, ); assert.equal(result.ok, true); if (!result.ok) return; assert.equal(result.workspace.workspaceId, fixtureId.workspace); assert.equal(result.workspace.id, fixtureId.workspaceRevision); assert.equal(result.workspace.interfaceImports.length, 3); assert.equal(result.workspace.conformances.length, 4); assert.equal("id" in result.workspace.conformances[0]!, false); }); test("conformances do not accept authored IDs", () => { const result = compileCapabilitySource( capabilityFixtureSource.replace( "conform Project as Named {", 'conform Project as Named id "conformance:project:named" {', ), "conformance-id.qx", ); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.phase === "syntax")); }); test("the v1 language has no implicit relationship materialization rule", () => { const result = compileCapabilitySource( capabilityFixtureSource.replace( /\n}\s*$/, '\n materialize ProjectOwner.owner if absent with Person;\n}\n', ), "materialize.qx", ); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.phase === "syntax")); }); test("forward declarations make source order irrelevant", () => { const source = capabilityFixtureSource .replace(/ atom Project[^\n]*\n/, "") .replace(/ atom Person[^\n]*\n/, "") .replace( /\n}\s*$/, '\n atom Project id "atom:project";\n atom Person id "atom:person";\n}\n', ); assert.equal(compileCapabilitySource(source, "reordered.qx").ok, true); }); test("interfaces can declare ordinary call operations", () => { const source = capabilityFixtureSource.replace( " value summary id \"member:summary:summary\" : string {\n get id \"operation:summary:summary:get\";\n }", " operation summarize id \"member:summary:summarize\" : string -> string {\n call id \"operation:summary:summarize\";\n }", ).replace( "bind summary.get to package TodoRuntime.summaryGet", "bind summarize.call to package TodoRuntime.summaryGet", ).replace( "operation summaryGet id \"export:todo-runtime:summary-get\" : unit -> string", "operation summaryGet id \"export:todo-runtime:summary-get\" : string -> string", ); const result = compileCapabilitySource(source, "operation-member.qx"); assert.equal(result.ok, true); if (!result.ok) return; const member = result.workspace.interfaceImports .find((entry) => entry.displayName === "Summary")?.members[0]; assert.equal(member?.kind, "operation"); }); test("state defaults accept recursive JSON values", () => { const source = capabilityFixtureSource.replace( ' policy optimistic-register default "Untitled project";', ` policy optimistic-register default "Untitled project"; shared state ProjectMetadata id "slot:project:metadata" on Project : message "example.Metadata" policy optimistic-register default {"labels":["compiler","runtime"],"score":1.5,"enabled":true,"extra":null};`, ); const result = compileCapabilitySource(source, "json-default.qx"); assert.equal(result.ok, true); if (!result.ok) return; const metadata = result.workspace.sharedAttachments.find( (entry) => entry.id === "slot:project:metadata", ); assert.equal(metadata?.kind, "state"); if (metadata?.kind !== "state") return; assert.deepEqual(metadata.defaultValue, { labels: ["compiler", "runtime"], score: 1.5, enabled: true, extra: null, }); }); test("syntax errors retain source locations", () => { const result = compileCapabilitySource( capabilityFixtureSource.replace("atom Project", "atom Project ???"), "broken.qx", ); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.phase === "syntax" && entry.fileName === "broken.qx" && entry.line > 0 )); }); test("unknown authoring names are lowering errors", () => { const result = compileCapabilitySource( capabilityFixtureSource.replace( "to state ProjectTitle.read", "to state NotAState.read", ), "unknown-name.qx", ); assert.equal(result.ok, false); if (result.ok) return; assert.ok(result.diagnostics.some((entry) => entry.phase === "lowering" && entry.code === "unknown-symbol" && entry.message.includes("NotAState") )); }); test("well-formed but invalid programs report semantic paths", () => { const result = compileCapabilitySource( capabilityFixtureSource.replace( "bind name.get to state ProjectTitle.read", "bind name.get to state ProjectTitle.write", ), "invalid-binding.qx", ); assert.equal(result.ok, false); if (result.ok) return; const diagnostic = result.diagnostics.find( (entry) => entry.code === "invalid-state-binding", ); assert.ok(diagnostic); assert.equal(diagnostic.phase, "validation"); assert.ok(diagnostic.path?.includes("operationBindings")); });