221 lines
7.3 KiB
TypeScript
221 lines
7.3 KiB
TypeScript
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\)/,
|
|
);
|
|
});
|