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.
72 lines
3.0 KiB
JavaScript
72 lines
3.0 KiB
JavaScript
// Q247 / row 324 (#1820): optional business tracker; role credential rules remain exact.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import { rmSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { loadBusiness, validateBusinessDocument } from "../src/index.mjs";
|
|
import { businessDoc, REPO, REPO_ROLES, systemConfig, tmp, writeJson } from "./helpers.mjs";
|
|
|
|
function fixture(t) {
|
|
const root = tmp();
|
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const doc = businessDoc(root);
|
|
const config = systemConfig(root);
|
|
const file = join(root, "config", "businesses", "acme.json");
|
|
return { root, doc, config, file };
|
|
}
|
|
|
|
const check = (x) => validateBusinessDocument(x.doc, x.file, { rolesDir: REPO_ROLES });
|
|
|
|
test("Q247: omitting tracker loads as null and preserves independent role bots", (t) => {
|
|
const x = fixture(t);
|
|
delete x.doc.tracker;
|
|
writeJson(x.file, x.doc);
|
|
const b = loadBusiness("acme", { dir: join(x.root, "config"), rolesDir: REPO_ROLES });
|
|
assert.equal(b.tracker, null);
|
|
assert.equal(b.roles.pm.tracker.bot, x.doc.roles.pm.tracker.bot);
|
|
assert.equal(b.roles.pm.credentials.vikunja.file, x.doc.roles.pm.credentials.vikunja.file);
|
|
assert.equal(Object.hasOwn(x.doc, "tracker"), false);
|
|
});
|
|
|
|
test("Q247: real business validate passes without a sync credential reference or file", (t) => {
|
|
const x = fixture(t);
|
|
rmSync(x.doc.tracker.sync.credentials.vikunja.file);
|
|
delete x.doc.tracker;
|
|
writeJson(x.file, x.doc);
|
|
const r = spawnSync(process.execPath, [join(REPO, "packages/organization/src/cli.mjs"), "validate", "acme"], {
|
|
env: { ...process.env, HOME: x.root, MOSAIC_CONFIG: x.config, MOSAIC_ROLES_DIR: REPO_ROLES },
|
|
encoding: "utf8", timeout: 30000,
|
|
});
|
|
assert.equal(r.status, 0, r.stdout + r.stderr);
|
|
assert.equal(JSON.parse(r.stdout).business, "acme");
|
|
assert.doesNotMatch(r.stderr, /credential reference problem|not found/);
|
|
});
|
|
|
|
test("Q247: present tracker retains its values and validation without mutating the input", (t) => {
|
|
const x = fixture(t);
|
|
const before = structuredClone(x.doc);
|
|
const b = check(x);
|
|
assert.equal(b.tracker.sync.bot, x.doc.tracker.sync.bot);
|
|
assert.equal(b.tracker.sync.botId, x.doc.tracker.sync.botId);
|
|
assert.equal(b.tracker.sync.credentials.vikunja.file, x.doc.tracker.sync.credentials.vikunja.file);
|
|
assert.deepEqual(b.tracker.labels, {});
|
|
assert.deepEqual(x.doc, before);
|
|
assert.ok(Object.isFrozen(b.tracker.sync));
|
|
});
|
|
|
|
test("Q247: present null, undefined and malformed trackers still refuse", (t) => {
|
|
const x = fixture(t);
|
|
for (const tracker of [null, undefined, false, [], {}, { sync: {} }]) {
|
|
x.doc.tracker = tracker;
|
|
assert.throws(() => check(x), /tracker/, String(tracker));
|
|
}
|
|
});
|
|
|
|
test("Q247: absent business tracker does not waive a role's Vikunja bot requirement", (t) => {
|
|
const x = fixture(t);
|
|
delete x.doc.tracker;
|
|
delete x.doc.roles.pm.tracker;
|
|
assert.throws(() => check(x), /roles\.pm needs "tracker"/);
|
|
});
|