Files
quixos-protocol/test/capability-assembly.test.ts
T

268 lines
9.9 KiB
TypeScript
Raw Normal View History

import assert from "node:assert/strict";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { compileCapabilityResourceRepository, compileWorkspaceRepository } from "../src/capability-language/index.js";
const quixosCommit = "1".repeat(40);
const namedCommit = "2".repeat(40);
const packageCommit = "3".repeat(40);
const lock = (resources = "") => `quixos-lock version 1 {
quixos source {
repository "https://example.test/quixos.git";
commit "${quixosCommit}";
}
${resources}
}
`;
test("workspace assembly resolves resource-owned dependencies recursively", async (context) => {
const directory = await mkdtemp(path.join(os.tmpdir(), "quixos-assembly-"));
context.after(() => rm(directory, { recursive: true, force: true }));
const root = path.join(directory, "root");
const named = path.join(directory, "named");
const runtime = path.join(directory, "runtime");
await Promise.all([mkdir(root), mkdir(named), mkdir(runtime)]);
await writeFile(
path.join(root, "quixos.lock"),
lock(`package Runtime source {
repository "https://example.test/package-runtime.git";
commit "${packageCommit}";
}`),
);
await writeFile(
path.join(root, "workspace.qx"),
`workspace Test id "workspace:test" revision "workspace:test@1" commit "${"4".repeat(40)}" {
atom Subject id "atom:subject";
import package Runtime;
}
`,
);
await writeFile(path.join(named, "quixos.lock"), lock());
await writeFile(
path.join(named, "interface.qx"),
`interface Named id "interface:named" revision "interface:named@1" {
value name id "member:named:name" : string {
get id "operation:named:name:get";
}
}
`,
);
await writeFile(
path.join(runtime, "quixos.lock"),
lock(`interface Named source {
repository "https://example.test/interface-named.git";
commit "${namedCommit}";
}`),
);
await writeFile(
path.join(runtime, "package.qx"),
`import interface Named;
package Runtime id "package:runtime" revision "package:runtime@1" {
function describe id "export:runtime:describe" : interface-ref<Named> -> string;
}
`,
);
const directories = new Map([
[`interface\0https://example.test/interface-named.git\0${namedCommit}`, named],
[`package\0https://example.test/package-runtime.git\0${packageCommit}`, runtime],
]);
const result = await compileWorkspaceRepository({
rootDirectory: root,
resolveResource: async (source, kind) => {
const resolved = directories.get(`${kind}\0${source.repository}\0${source.commit}`);
if (!resolved) throw new Error(`Unexpected source ${source.repository}`);
return { directory: resolved };
},
});
assert.equal(result.resources.length, 2);
assert.deepEqual(
result.workspace.interfaceImports.map((entry) => entry.revisionId),
["interface:named@1"],
);
assert.deepEqual(
result.workspace.packageImports.map((entry) => entry.revisionId),
["package:runtime@1"],
);
assert.equal(result.directResources.size, 1);
const resource = await compileCapabilityResourceRepository({
rootDirectory: runtime,
kind: "package",
source: {
resolver: "git",
repository: "https://example.test/package-runtime.git",
commit: packageCommit,
},
resolveResource: async (source, kind) => {
const resolved = directories.get(`${kind}\0${source.repository}\0${source.commit}`);
if (!resolved) throw new Error(`Unexpected source ${source.repository}`);
return { directory: resolved };
},
});
assert.equal(resource.resource.kind, "package");
assert.equal(resource.resources.length, 2);
assert.equal(resource.directResources.size, 1);
});
test("resource repositories cannot own workspace Quixos selection policy", async (context) => {
const directory = await mkdtemp(path.join(os.tmpdir(), "quixos-resource-policy-"));
context.after(() => rm(directory, { recursive: true, force: true }));
await writeFile(
path.join(directory, "quixos.lock"),
`quixos-lock version 1 {
quixos source {
repository "https://example.test/quixos.git";
policy track-development;
ref "dev/alice/main";
commit "${quixosCommit}";
}
}`,
);
await writeFile(
path.join(directory, "package.qx"),
`package Runtime id "package:runtime" revision "package:runtime@1" { }\n`,
);
await assert.rejects(
compileCapabilityResourceRepository({
rootDirectory: directory,
kind: "package",
source: {
resolver: "git",
repository: "https://example.test/package-runtime.git",
commit: packageCommit,
},
resolveResource: async () => ({ directory }),
}),
/resource locks may only declare their exact authored-against commit/,
);
});
test("resource manifests cannot hide lock dependencies", async (context) => {
const directory = await mkdtemp(path.join(os.tmpdir(), "quixos-assembly-mismatch-"));
context.after(() => rm(directory, { recursive: true, force: true }));
const root = path.join(directory, "root");
const runtime = path.join(directory, "runtime");
await Promise.all([mkdir(root), mkdir(runtime)]);
await writeFile(
path.join(root, "quixos.lock"),
lock(`package Runtime source {
repository "https://example.test/package-runtime.git";
commit "${packageCommit}";
}`),
);
await writeFile(
path.join(root, "workspace.qx"),
`workspace Test id "workspace:test" revision "workspace:test@1" commit "${"4".repeat(40)}" {
atom Subject id "atom:subject";
import package Runtime;
}
`,
);
await writeFile(path.join(runtime, "quixos.lock"), lock());
await writeFile(
path.join(runtime, "package.qx"),
`import interface Hidden;
package Runtime id "package:runtime" revision "package:runtime@1" { }
`,
);
await assert.rejects(
compileWorkspaceRepository({
rootDirectory: root,
resolveResource: async () => ({ directory: runtime }),
}),
/No resolved interface is available for lock binding Hidden/,
);
});
test("workspace assembly must satisfy nominal external interfaces", async (context) => {
const directory = await mkdtemp(path.join(os.tmpdir(), "quixos-assembly-external-"));
context.after(() => rm(directory, { recursive: true, force: true }));
const root = path.join(directory, "root");
const runtime = path.join(directory, "runtime");
await Promise.all([mkdir(root), mkdir(runtime)]);
await writeFile(
path.join(root, "quixos.lock"),
lock(`package Runtime source {
repository "https://example.test/package-runtime.git";
commit "${packageCommit}";
}`),
);
await writeFile(
path.join(root, "workspace.qx"),
`workspace Test id "workspace:test" revision "workspace:test@1" commit "${"4".repeat(40)}" {
atom Subject id "atom:subject";
import package Runtime;
}
`,
);
await writeFile(path.join(runtime, "quixos.lock"), lock());
await writeFile(
path.join(runtime, "package.qx"),
`external interface Named revision "interface:named@1";
package Runtime id "package:runtime" revision "package:runtime@1" { }
`,
);
await assert.rejects(
compileWorkspaceRepository({
rootDirectory: root,
resolveResource: async () => ({ directory: runtime }),
}),
/requires external interface Named \(interface:named@1\)/,
);
});
test("resource-owned fragment imports compile through the locked package closure", async (context) => {
const directory = await mkdtemp(path.join(os.tmpdir(), "quixos-fragment-assembly-"));
context.after(() => rm(directory, { recursive: true, force: true }));
const dirs = Object.fromEntries(["item", "rows", "screen"].map((name) => [name, path.join(directory, name)]));
await Promise.all(Object.values(dirs).map((dir) => mkdir(dir)));
const dependency = (kind: string, name: string) =>
`${kind} ${name} source {repository "https://example.test/${name.toLowerCase()}.git"; commit "${namedCommit}";}`;
await writeFile(path.join(dirs.item!, "quixos.lock"), lock());
await writeFile(
path.join(dirs.item!, "interface.qx"),
`interface Item id "item" revision "item@1" {queryable value title id "title" : string {get id "title:get" execution snapshot;}}`,
);
await writeFile(path.join(dirs.rows!, "quixos.lock"), lock(dependency("interface", "Item")));
await writeFile(
path.join(dirs.rows!, "package.qx"),
`import interface Item; package Rows id "rows" revision "rows@1" {query fragment Row id "row" root Item document "row.graphql" fragment "Row";}`,
);
await writeFile(path.join(dirs.rows!, "row.graphql"), "fragment Row on Item {_camino {ref} title}");
await writeFile(
path.join(dirs.screen!, "quixos.lock"),
lock(dependency("interface", "Item") + dependency("package", "Rows")),
);
await writeFile(
path.join(dirs.screen!, "package.qx"),
`import interface Item; import package Rows; package Screen id "screen" revision "screen@1" {
query Inputs id "inputs" root Item document "screen.graphql" operation "Screen" {replica; fragments Rows.Row;}
query View id "view" root Item document "screen.graphql" operation "Screen" {local from Inputs; fragments Rows.Row;}
}`,
);
await writeFile(path.join(dirs.screen!, "screen.graphql"), "query Screen {root {...Row}}");
const result = await compileCapabilityResourceRepository({
rootDirectory: dirs.screen!,
kind: "package",
source: { resolver: "git", repository: "https://example.test/screen.git", commit: packageCommit },
resolveResource: async (source) => {
const name = new URL(source.repository).pathname.slice(1, -4);
assert.ok(dirs[name], name);
return { directory: dirs[name]! };
},
});
assert.equal(result.resource.kind, "package");
if (result.resource.kind !== "package") throw Error("expected package");
const view = result.resource.revision.checkedQueries!.find((q) => q.declaration.id === "view")!;
assert.equal(view.selection[0]!.selection[0]!.fragmentSpread?.package, "rows@1");
assert.equal(result.resources.length, 3);
});