git mv packages/business -> packages/organization (@mosaic/organization). The old package stays as one-line re-export shims of each moved module, with src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs forwarding to the moved executable. BusinessError is OrganizationConfigError (one constructor, name kept BusinessError); OrganizationError stays distinct. The bus adapter gains organization-context.mjs (busOrganization), with business.mjs (busBusiness) as the same function. Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure walker (organization-imports.mjs, both roots required) move to the new path. v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage A contract tests stay where they were and pass as before.
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, {});
|
|
});
|