Darkwing's round 2 candidate, approved by Filbert (#1518 comment 26730). build-r2.patch a27890d5, manifest 869168c7, 34 files, applied on HEAD and checked 34/34. Integration gate on an export of HEAD plus the patch: business 60/60 on Node 24 and 26, every package test and every scripts/test-*.sh green, test-task 98/98 with the live-provider cases. Conductor, queue, conversation and discord confirmed in git worktrees of HEAD with and without the patch, identical results. Lead decision 63 accepts the vocabulary location, the example path and the business branch. Co-Authored-By: Claude Opus 5.5 <[email protected]>
49 lines
2.3 KiB
JavaScript
49 lines
2.3 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { join } from "node:path";
|
|
import { BusinessError, loadProject, projectFilePath, validateProjectDocument } from "../src/index.mjs";
|
|
import { tmp, writeJson } from "./helpers.mjs";
|
|
|
|
function refuses(fn, pattern, exitCode = 2) {
|
|
assert.throws(fn, (error) => {
|
|
assert.ok(error instanceof BusinessError, `expected BusinessError, got ${error}`);
|
|
assert.equal(error.exitCode, exitCode);
|
|
assert.match(error.message, pattern);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
const doc = (extra = {}) => ({
|
|
projectVersion: 1, id: "stack",
|
|
vars: { "tracker.project": 3, "git.workingBranch": "refactor", "git.protectedBranches": ["main", "next"] },
|
|
roles: { coder: { vars: { "limits.tools": ["read", "bash"] } } },
|
|
...extra,
|
|
});
|
|
|
|
test("path and load", () => {
|
|
assert.equal(projectFilePath("/w/stack"), "/w/stack/.mosaic/project.json");
|
|
const root = tmp();
|
|
writeJson(projectFilePath(root), doc());
|
|
const project = loadProject(root);
|
|
assert.equal(project.id, "stack");
|
|
assert.equal(project.file, projectFilePath(root));
|
|
assert.deepEqual(project.roles.coder.vars["limits.tools"], ["read", "bash"]);
|
|
assert.ok(Object.isFrozen(project.vars));
|
|
});
|
|
|
|
test("refusals", () => {
|
|
refuses(() => loadProject("relative"), /normalized absolute path/);
|
|
refuses(() => loadProject("/a/../b"), /normalized absolute path/);
|
|
refuses(() => loadProject(tmp()), /project file not found/, 4);
|
|
const v = (d) => validateProjectDocument(d, "/p/.mosaic/project.json");
|
|
refuses(() => v(doc({ projectVersion: 2 })), /projectVersion/);
|
|
refuses(() => v(doc({ id: "Stack" })), /project id/);
|
|
refuses(() => v(doc({ owner: "x" })), /unsupported project file key: "owner"/);
|
|
refuses(() => v(doc({ vars: { harness: "pi" } })), /harness can't be set at the project layer/);
|
|
refuses(() => v(doc({ vars: { "tracker.baseUrl": "http://x" } })), /project layer/);
|
|
refuses(() => v(doc({ roles: { coder: { vars: { model: "x" } } } })), /roles\.coder\.vars: variable model can't be set at the project layer/);
|
|
refuses(() => v(doc({ roles: { coder: { holder: "x" } } })), /unsupported .*roles\.coder key: "holder"/);
|
|
refuses(() => v(doc({ roles: { "Coder!": {} } })), /role instance/);
|
|
assert.deepEqual(v(doc({ vars: undefined, roles: undefined })).roles, {});
|
|
});
|