Files
stack/packages/runs/tests/task-cli.test.mjs
T
jason.woltjeandClaude Opus 5.5 0a9692aebd runs: list survives a damaged run record (#1551)
Row 82 round 2, owner Rocko, candidate manifest dbf36e92 on 876f1ffe.
Approved by Filbert (27650); Sage's gate green 02:05-02:09Z.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 21:13:56 -05:00

140 lines
6.6 KiB
JavaScript

// 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("list prints a damaged result.json as unknown and keeps listing the runs after it", (t) => {
const { dataRoot } = scratch(t);
const M = "r-20260101T000050Z-mmmmmm";
write(path.join(dataRoot, "runs", A, "result.json"), { ...RESULT, workspace: "ws1", session: "s1" });
write(path.join(dataRoot, "runs", M, "result.json"), {});
write(path.join(dataRoot, "runs", B, "result.json"), { ...RESULT, taskId: "t-two", workspace: ":run" });
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` +
`${M} unknown task=- ws=- session=-\n` +
`${B} succeeded task=t-two ws=:run session=-\n`);
});
test("list prints each result field that isn't a string as unknown or -", (t) => {
const { dataRoot } = scratch(t);
const runs = [
["r-20260101T000000Z-status", { status: 5 }, "unknown task=t-one ws=- session=-"],
["r-20260101T000001Z-taskid", { taskId: null }, "succeeded task=- ws=- session=-"],
["r-20260101T000002Z-worksp", { workspace: 7 }, "succeeded task=t-one ws=- session=-"],
["r-20260101T000003Z-sessin", { session: { name: "s1" } }, "succeeded task=t-one ws=- session=-"],
];
for (const [runId, damage] of runs) write(path.join(dataRoot, "runs", runId, "result.json"), { ...RESULT, ...damage });
const out = task(t, dataRoot, "list");
assert.equal(out.status, 0, out.stderr);
assert.equal(out.stdout, runs.map(([runId, , line]) => `${runId} ${line}\n`).join(""));
});
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-<id>)\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-<id>)\n');
});