Build workspace agent, capability graph, and versioned cutovers
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
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\)/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user