// `scripts/mosaic queue` (A2). The dispatch must leave every other call to // scripts/mosaic exactly as it was: the seat CLI sees the same argv, working // directory, environment and stdin under the new script as under the pre-A2 // copy in fixtures/mosaic-pre-a2.sh. Both CLIs are fakes that record what // they got; nothing here runs a seat or touches a queue. import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { test } from "node:test"; import { SCRIPTS } from "./helpers.mjs"; const HERE = new URL(".", import.meta.url).pathname; const FAKE = `import { readFileSync, writeFileSync } from "node:fs"; writeFileSync(process.env.CAPTURE, JSON.stringify({ cli: process.argv[1].slice(process.env.ROOT.length), argv: process.argv.slice(2), cwd: process.cwd(), env: process.env, stdin: readFileSync(0, "utf8"), })); process.stdout.write("fake out\\n"); process.stderr.write("fake err\\n"); process.exitCode = Number(process.env.EXIT); `; // A root holding `script` as scripts/mosaic and both fake CLIs. function root(t, script) { const r = realpathSync(mkdtempSync(join(tmpdir(), "mosaic-queue-dispatch-"))); t.after(() => rmSync(r, { recursive: true, force: true })); mkdirSync(join(r, "scripts")); cpSync(script, join(r, "scripts/mosaic")); for (const pkg of ["seat", "queue"]) { mkdirSync(join(r, `packages/${pkg}/src`), { recursive: true }); writeFileSync(join(r, `packages/${pkg}/src/cli.mjs`), FAKE); } mkdirSync(join(r, "work")); return r; } // Runs scripts/mosaic from `work` and returns everything the fake saw, with // the root replaced so two roots compare. function call(r, args, exit = 7) { const capture = join(r, "capture.json"); rmSync(capture, { force: true }); const env = { PATH: process.env.PATH, HOME: r, LANG: "C", ROOT: r, CAPTURE: capture, EXIT: String(exit), MOSAIC_AGENT_NAME: "darkwing", SPACED: "a b" }; const p = spawnSync("bash", [join(r, "scripts/mosaic"), ...args], { cwd: join(r, "work"), env, input: "stdin line\nsecond\n", encoding: "utf8" }); if (p.error) throw p.error; const got = existsSync(capture) ? readFileSync(capture, "utf8") : null; return { status: p.status, out: p.stdout, err: p.stderr, capture: got === null ? null : got.replaceAll(r, "") }; } const SEAT_CALLS = [ ["launch", "--repo", "/srv/repo", "--harness", "pi", "darkwing"], ["seat", "task", "darkwing", "text with two spaces", ""], [], [""], ["launch", "queue"], ["Queue", "list"], ["queue-x"], ["--", "queue"], ["--help"], ]; test("every call but `queue` reaches the seat CLI exactly as before A2", (t) => { const before = root(t, join(HERE, "fixtures", "mosaic-pre-a2.sh")); const after = root(t, join(SCRIPTS, "mosaic")); for (const args of SEAT_CALLS) { const a = call(before, args); const b = call(after, args); assert.notEqual(a.capture, null, `${JSON.stringify(args)}: the pre-A2 script reached no CLI`); assert.equal(JSON.parse(a.capture).cli, "/packages/seat/src/cli.mjs"); assert.deepEqual(b, a, JSON.stringify(args)); } }); test("`queue` reaches the queue CLI with the rest of the arguments", (t) => { const r = root(t, join(SCRIPTS, "mosaic")); for (const rest of [["list"], [], ["note", "9", "text with spaces", "--op", "note-9-000001"], ["queue"], [""]]) { const got = call(r, ["queue", ...rest]); assert.deepEqual([got.status, got.out, got.err], [7, "fake out\n", "fake err\n"]); const c = JSON.parse(got.capture); assert.deepEqual([c.cli, c.argv, c.cwd, c.stdin], ["/packages/queue/src/cli.mjs", rest, "/work", "stdin line\nsecond\n"]); } // A queue CLI that succeeds must not fall through to the seat CLI. const ok = call(r, ["queue", "list"], 0); assert.deepEqual([ok.status, ok.out, JSON.parse(ok.capture).cli], [0, "fake out\n", "/packages/queue/src/cli.mjs"]); }); test("the pre-A2 fixture is the script A2 changed", () => { const fixture = readFileSync(join(HERE, "fixtures", "mosaic-pre-a2.sh"), "utf8"); const now = readFileSync(join(SCRIPTS, "mosaic"), "utf8"); // A2 adds lines and removes none. const lines = now.split("\n"); let i = 0; for (const l of fixture.split("\n")) { while (i < lines.length && lines[i] !== l) i++; assert.ok(i < lines.length, `pre-A2 line missing: ${l}`); i++; } });