Files
stack/packages/cli/tests/channel-fixture.mjs
jason.woltje a8d9eecf65 organization: move packages/business to packages/organization (rename r2 stage B1)
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.
2026-11-09 11:47:48 -06:00

82 lines
4.3 KiB
JavaScript

// Private fixture runtime only; no live host, unit, store or organization file.
import { spawn } from "node:child_process";
import { once } from "node:events";
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { fixture, BUSINESSES } from "./helpers.mjs";
import { systemFor } from "../../organization/tests/helpers.mjs";
import { reserveOrg, activateOrg } from '../../organization/src/org-registry.mjs';
export async function childResult(file, args, env, input) {
const child = spawn(file, args, { env, stdio: ["pipe", "pipe", "pipe"] });
let stdout = "", stderr = "";
child.stdout.on("data", b => stdout += b);
child.stderr.on("data", b => stderr += b);
child.stdin.on("error", () => {});
child.stdin.end(input);
const [status, signal] = await once(child, "close");
return { status, signal, stdout, stderr };
}
export async function provedRuntime(t) {
const root = mkdtempSync(join(tmpdir(), "channel-recovery-"));
let host;
t.after(async () => {
try {
if (host && host.exitCode === null && host.signalCode === null) {
const done = once(host, "close");
const timer = setTimeout(() => host.kill("SIGKILL"), 3000);
host.kill("SIGTERM");
try { await done; } finally { clearTimeout(timer); }
}
} finally { rmSync(root, { recursive: true, force: true }); }
});
const f = fixture(root, "demo", doc => ({ ...doc, plugins: { channel: [] } }));
mkdirSync(join(root, '.config'), { mode: 0o700 });
const configDir = join(root, '.config', 'mosaic-test');
renameSync(join(root, 'config'), configDir); chmodSync(configDir, 0o700);
writeFileSync(join(f.dataRoot, 'instance.json'), JSON.stringify({ instanceVersion: 1, name: 'test' }));
const ctx = { configDir, dataRoot: f.dataRoot, instance: 'test' };
const org = await reserveOrg(ctx, 'demo'); await activateOrg(ctx, 'demo');
const url = rel => new URL(rel, import.meta.url).href;
const humanURL = url("../../bus/src/human.mjs");
const script = join(root, "fixture-runtime.mjs");
// As in stack-copy.test.mjs, isolate the fixture's ancestry from the seat.
// The real PID, UID, argv path, start time, nonce and verifier still run.
writeFileSync(script, `
import { mock } from 'node:test';
const real = await import(${JSON.stringify(humanURL)});
mock.module(${JSON.stringify(humanURL)}, { namedExports: {
readProcess: real.readProcess,
verifyHuman: (proof, options) => real.verifyHuman(proof, { ...options,
readProcess: pid => ({ ...real.readProcess(pid), ppid: 1 }) }),
} });
const { startBroker } = await import(${JSON.stringify(url("../../bus/src/runtime.mjs"))});
const runtime = await startBroker({ dataRoot: ${JSON.stringify(org.root)},
businesses: ${JSON.stringify(BUSINESSES)},
organization: { dir: ${JSON.stringify(configDir)}, rolesDir: ${JSON.stringify(f.roles)}, system: ${JSON.stringify(systemFor(root))} } });
process.on('SIGTERM', async () => { await runtime.close(); process.exit(0); });
console.log('fixture ready');
`);
const env = { PATH: process.env.PATH, HOME: root, TMPDIR: process.env.TMPDIR,
MOSAIC_CONFIG: join(configDir, 'config.json'), MOSAIC_ROLES_DIR: f.roles };
host = spawn(process.execPath, ["--experimental-test-module-mocks", script], { env, stdio: ["ignore", "pipe", "pipe"] });
let output = "", errors = "";
host.stderr.on("data", b => errors += b);
await new Promise((resolve, reject) => {
host.stdout.on("data", b => { output += b; if (output.includes("fixture ready")) resolve(); });
host.once("exit", code => reject(new Error(`fixture failed ${code}: ${errors}`)));
host.once("error", reject);
});
const cli = fileURLToPath(new URL("../src/cli.mjs", import.meta.url));
const shim = fileURLToPath(new URL("../../bus/src/human-cli.mjs", import.meta.url));
return { root, f, env, shim, socket: join(org.root, "bus/broker.sock"),
cli: argv => childResult(process.execPath, [cli, ...argv, "--business", "demo"], env),
shimCall: (file, request) => childResult(process.execPath, [file, join(org.root, "bus/broker.sock")], env, JSON.stringify(request) + "\n"),
file: join(configDir, "businesses/demo.json"),
history: () => readFileSync(join(configDir, "businesses/.history/demo/changes.jsonl"), "utf8").trim().split("\n").map(JSON.parse),
};
}