// scripts/mosaic-task.mjs list and show read through this package. These // spawn the real script against a seeded data root and a scratch config. import { test } from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import { RESULT, scratch, write } from "./helpers.mjs"; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", ".."); const A = "r-20260101T000000Z-aaaaaa"; const B = "r-20260101T000100Z-bbbbbb"; function task(t, dataRoot, ...args) { const config = path.join(path.dirname(dataRoot), "config.json"); write(config, { configVersion: 1, environment: "development", dataRoot, execution: { backend: "docker", provider: "zai", model: "m" } }); const env = { ...process.env, MOSAIC_CONFIG: config }; delete env.NODE_TEST_CONTEXT; const proc = spawnSync(process.execPath, [path.join(ROOT, "scripts", "mosaic-task.mjs"), ...args], { cwd: ROOT, env, encoding: "utf8" }); return { status: proc.status, stdout: proc.stdout, stderr: proc.stderr }; } test("list prints each run in the established format", (t) => { const { dataRoot } = scratch(t); write(path.join(dataRoot, "runs", A, "result.json"), { ...RESULT, workspace: "ws1", session: "s1" }); write(path.join(dataRoot, "runs", B, "task.json"), { id: "t" }); const out = task(t, dataRoot, "list"); assert.equal(out.status, 0, out.stderr); assert.equal(out.stdout, `${A} succeeded task=t-one ws=ws1 session=s1\n` + `${B} unknown task=- ws=- session=-\n`); }); test("show prints the run in the established format", (t) => { const { dataRoot } = scratch(t); const dir = path.join(dataRoot, "runs", A); write(path.join(dir, "result.json"), { ...RESULT, missionId: "m", tools: ["read"], expectedExact: "hi" }); write(path.join(dir, "mission.json"), { id: "m", objective: "obj" }); const out = task(t, dataRoot, "show", A); assert.equal(out.status, 0, out.stderr); assert.equal(out.stdout, [ `run: ${A}`, "status: succeeded", "task: t-one", "mission: m", "tools: read", "adapter: (see config) provider=zai model=m", 'request: "hi"', 'response: "hi"', 'expected: "hi"', "timing: 2026-01-01T00:00:00.000Z -> 2026-01-01T00:00:01.000Z (1000 ms)", "exit: 0", "mission snapshot: m - obj", `artifacts: ${fs.readdirSync(dir).join(", ")}`, "", ].join("\n")); }); test("show of a missing run and an invalid id exit 4 as before", (t) => { const { dataRoot } = scratch(t); const missing = task(t, dataRoot, "show", A); assert.equal(missing.status, 4); assert.equal(missing.stderr, `mosaic-task: run not found: ${A} (under ${path.join(dataRoot, "runs")})\n`); const invalid = task(t, dataRoot, "show", "../x"); assert.equal(invalid.status, 4); assert.equal(invalid.stderr, 'mosaic-task: invalid run id: "../x" (expected r-)\n'); }); test("list and show refuse a runs directory linked out of the data root", (t) => { const { dataRoot, outside } = scratch(t); write(path.join(outside, A, "result.json"), RESULT); fs.symlinkSync(outside, path.join(dataRoot, "runs")); for (const args of [["list"], ["show", A]]) { const out = task(t, dataRoot, ...args); assert.equal(out.status, 4, args.join(" ")); assert.equal(out.stdout, ""); assert.match(out.stderr, /^mosaic-task: .*runs resolves outside the data root/); } }); test("show refuses a run linked out of the data root; list reports it unknown", (t) => { const { dataRoot, outside } = scratch(t); write(path.join(outside, "result.json"), RESULT); fs.mkdirSync(path.join(dataRoot, "runs")); fs.symlinkSync(outside, path.join(dataRoot, "runs", A)); const show = task(t, dataRoot, "show", A); assert.equal(show.status, 4); assert.equal(show.stdout, ""); assert.match(show.stderr, /resolves outside the data root/); const list = task(t, dataRoot, "list"); assert.equal(list.status, 0, list.stderr); assert.equal(list.stdout, `${A} unknown task=- ws=- session=-\n`); }); test("list shows an r- name that isn't a valid run id, as before", (t) => { const { dataRoot } = scratch(t); write(path.join(dataRoot, "runs", "r-.bad", "result.json"), RESULT); const out = task(t, dataRoot, "list"); assert.equal(out.status, 0, out.stderr); assert.equal(out.stdout, "r-.bad succeeded task=t-one ws=- session=-\n"); }); test("show refuses an invalid id before reading the config", (t) => { const { dir } = scratch(t); const env = { ...process.env, MOSAIC_CONFIG: path.join(dir, "missing.json") }; delete env.NODE_TEST_CONTEXT; const proc = spawnSync(process.execPath, [path.join(ROOT, "scripts", "mosaic-task.mjs"), "show", "../x"], { cwd: ROOT, env, encoding: "utf8" }); assert.equal(proc.status, 4, proc.stderr); assert.equal(proc.stderr, 'mosaic-task: invalid run id: "../x" (expected r-)\n'); });