235 lines
7.8 KiB
TypeScript
235 lines
7.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import {
|
|
compileWorkspaceRevision,
|
|
computeCapabilityClosure,
|
|
resolveOperationPlan,
|
|
validateWorkspaceRevision,
|
|
valueType,
|
|
type CapabilityValidationIssueCode,
|
|
type WorkspaceRevision,
|
|
} from "../src/capability-model/index.js";
|
|
import {
|
|
fixtureId,
|
|
makeValidCapabilityWorkspace,
|
|
} from "./fixtures/capability-model.js";
|
|
|
|
const expectIssue = (
|
|
workspace: WorkspaceRevision,
|
|
code: CapabilityValidationIssueCode,
|
|
) => {
|
|
const issues = validateWorkspaceRevision(workspace);
|
|
assert.ok(
|
|
issues.some((entry) => entry.code === code),
|
|
`Expected ${code}, got:\n${issues
|
|
.map((entry) => `${entry.code} ${entry.path}: ${entry.message}`)
|
|
.join("\n")}`,
|
|
);
|
|
assert.equal(compileWorkspaceRevision(workspace).ok, false);
|
|
};
|
|
|
|
const conformance = (
|
|
workspace: WorkspaceRevision,
|
|
identity: Pick<(typeof workspace.conformances)[number], "atomId" | "interfaceRevisionId">,
|
|
) => {
|
|
const result = workspace.conformances.find((entry) =>
|
|
entry.atomId === identity.atomId &&
|
|
entry.interfaceRevisionId === identity.interfaceRevisionId
|
|
);
|
|
assert.ok(
|
|
result,
|
|
`Missing fixture conformance ${identity.atomId} as ${identity.interfaceRevisionId}`,
|
|
);
|
|
return result;
|
|
};
|
|
|
|
test("the representative v1 workspace compiles to native and package plans", () => {
|
|
const workspace = makeValidCapabilityWorkspace();
|
|
assert.deepEqual(validateWorkspaceRevision(workspace), []);
|
|
const compiled = compileWorkspaceRevision(workspace);
|
|
assert.equal(compiled.ok, true);
|
|
if (!compiled.ok) return;
|
|
|
|
const title = resolveOperationPlan(
|
|
compiled.plan,
|
|
fixtureId.project,
|
|
fixtureId.namedV1,
|
|
fixtureId.namedGet,
|
|
);
|
|
assert.equal(title?.kind, "state");
|
|
if (title?.kind === "state") {
|
|
assert.equal(title.binding.slotId, fixtureId.projectTitle);
|
|
assert.equal(title.binding.primitive, "read");
|
|
assert.deepEqual(title.attachment.owner, { kind: "workspace" });
|
|
}
|
|
|
|
const owner = resolveOperationPlan(
|
|
compiled.plan,
|
|
fixtureId.project,
|
|
fixtureId.ownedV1,
|
|
fixtureId.ownerResolve,
|
|
);
|
|
assert.equal(owner?.kind, "edge");
|
|
if (owner?.kind === "edge") {
|
|
assert.equal(owner.binding.edgeTypeId, fixtureId.projectOwner);
|
|
assert.deepEqual(owner.attachment.owner, {
|
|
kind: "conformance",
|
|
...fixtureId.projectOwnedConformance,
|
|
});
|
|
}
|
|
|
|
const summary = resolveOperationPlan(
|
|
compiled.plan,
|
|
fixtureId.project,
|
|
fixtureId.summaryV1,
|
|
fixtureId.summaryGet,
|
|
);
|
|
assert.equal(summary?.kind, "package");
|
|
if (summary?.kind === "package") {
|
|
assert.equal(summary.packageRevision.revisionId, fixtureId.todoRuntimeV1);
|
|
assert.equal(summary.packageExport.id, fixtureId.summaryGetExport);
|
|
assert.deepEqual(
|
|
summary.dependencies.map((entry) => [entry.port.id, entry.binding.kind]),
|
|
[
|
|
[fixtureId.titlePort, "state"],
|
|
[fixtureId.namedPort, "receiver-interface"],
|
|
[fixtureId.personPort, "constructor"],
|
|
],
|
|
);
|
|
}
|
|
});
|
|
|
|
test("the closure is an exact tree-shaking boundary", () => {
|
|
const result = compileWorkspaceRevision(makeValidCapabilityWorkspace());
|
|
assert.equal(result.ok, true);
|
|
if (!result.ok) return;
|
|
const closure = computeCapabilityClosure(result.plan, [
|
|
{ atomId: fixtureId.project, interfaceRevisionId: fixtureId.summaryV1 },
|
|
]);
|
|
assert.deepEqual(closure.conformances, [
|
|
fixtureId.projectNamedConformance,
|
|
fixtureId.projectSummaryConformance,
|
|
]);
|
|
assert.deepEqual(closure.packageRevisionIds, [fixtureId.todoRuntimeV1]);
|
|
assert.deepEqual(closure.attachmentIds, [fixtureId.projectTitle]);
|
|
assert.deepEqual(closure.constructorAtomIds, [fixtureId.person]);
|
|
});
|
|
|
|
test("compiled plans are snapshots, not mutable authoring state", () => {
|
|
const workspace = makeValidCapabilityWorkspace();
|
|
const result = compileWorkspaceRevision(workspace);
|
|
assert.equal(result.ok, true);
|
|
if (!result.ok) return;
|
|
conformance(workspace, fixtureId.projectNamedConformance)
|
|
.operationBindings[0]!.binding = {
|
|
kind: "state",
|
|
slotId: fixtureId.personName,
|
|
primitive: "read",
|
|
};
|
|
const resolved = resolveOperationPlan(
|
|
result.plan,
|
|
fixtureId.project,
|
|
fixtureId.namedV1,
|
|
fixtureId.namedGet,
|
|
);
|
|
assert.equal(resolved?.kind, "state");
|
|
if (resolved?.kind === "state") {
|
|
assert.equal(resolved.binding.slotId, fixtureId.projectTitle);
|
|
}
|
|
});
|
|
|
|
test("a conformance binds every operation exactly once", () => {
|
|
const missing = makeValidCapabilityWorkspace();
|
|
conformance(missing, fixtureId.projectNamedConformance).operationBindings.pop();
|
|
expectIssue(missing, "missing-operation-binding");
|
|
|
|
const duplicate = makeValidCapabilityWorkspace();
|
|
const bindings = conformance(
|
|
duplicate,
|
|
fixtureId.projectNamedConformance,
|
|
).operationBindings;
|
|
bindings.push(structuredClone(bindings[0]!));
|
|
expectIssue(duplicate, "duplicate-operation-binding");
|
|
});
|
|
|
|
test("private attachments are visible only to their owning conformance", () => {
|
|
const workspace = makeValidCapabilityWorkspace();
|
|
const binding = conformance(
|
|
workspace,
|
|
fixtureId.projectNamedConformance,
|
|
).operationBindings[0]!.binding;
|
|
assert.equal(binding.kind, "state");
|
|
if (binding.kind !== "state") return;
|
|
binding.slotId = fixtureId.personName;
|
|
expectIssue(workspace, "private-attachment-access");
|
|
});
|
|
|
|
test("native state and edge providers must match operation shape", () => {
|
|
const stateWorkspace = makeValidCapabilityWorkspace();
|
|
const state = conformance(
|
|
stateWorkspace,
|
|
fixtureId.projectNamedConformance,
|
|
).operationBindings[0]!.binding;
|
|
assert.equal(state.kind, "state");
|
|
if (state.kind === "state") state.primitive = "write";
|
|
expectIssue(stateWorkspace, "invalid-state-binding");
|
|
|
|
const edgeWorkspace = makeValidCapabilityWorkspace();
|
|
const edge = conformance(
|
|
edgeWorkspace,
|
|
fixtureId.projectOwnedConformance,
|
|
).operationBindings[0]!.binding;
|
|
assert.equal(edge.kind, "edge");
|
|
if (edge.kind === "edge") edge.primitive = "connect";
|
|
expectIssue(edgeWorkspace, "invalid-edge-binding");
|
|
});
|
|
|
|
test("package dependencies are complete, exact, and explicitly injected", () => {
|
|
const missing = makeValidCapabilityWorkspace();
|
|
const packageBinding = conformance(
|
|
missing,
|
|
fixtureId.projectSummaryConformance,
|
|
).operationBindings[0]!.binding;
|
|
assert.equal(packageBinding.kind, "package");
|
|
if (packageBinding.kind === "package") packageBinding.dependencies.pop();
|
|
expectIssue(missing, "invalid-dependency-binding");
|
|
|
|
const wrongType = makeValidCapabilityWorkspace();
|
|
const summaryExport = wrongType.packageImports[0]!.exports.find(
|
|
(entry) => entry.id === fixtureId.summaryGetExport,
|
|
);
|
|
assert.ok(summaryExport);
|
|
summaryExport.dependencyPorts[0]!.requirement = {
|
|
kind: "state",
|
|
valueType: valueType.int32,
|
|
primitives: ["read"],
|
|
};
|
|
expectIssue(wrongType, "invalid-dependency-binding");
|
|
});
|
|
|
|
test("package receiver requirements cannot depend on themselves", () => {
|
|
const workspace = makeValidCapabilityWorkspace();
|
|
const summaryExport = workspace.packageImports[0]!.exports.find(
|
|
(entry) => entry.id === fixtureId.summaryGetExport,
|
|
);
|
|
assert.ok(summaryExport && summaryExport.kind === "operation");
|
|
summaryExport.receiverRequirement = {
|
|
kind: "all-interfaces",
|
|
interfaceRevisionIds: [fixtureId.summaryV1],
|
|
};
|
|
summaryExport.dependencyPorts = [];
|
|
const packageBinding = conformance(
|
|
workspace,
|
|
fixtureId.projectSummaryConformance,
|
|
).operationBindings[0]!.binding;
|
|
assert.equal(packageBinding.kind, "package");
|
|
if (packageBinding.kind === "package") packageBinding.dependencies = [];
|
|
expectIssue(workspace, "cyclic-conformance-requirement");
|
|
});
|
|
|
|
test("constructors are statically checked", () => {
|
|
const badConstructor = makeValidCapabilityWorkspace();
|
|
badConstructor.constructors[0]!.exportId = fixtureId.summaryGetExport;
|
|
expectIssue(badConstructor, "invalid-constructor");
|
|
});
|