baa-conductor


baa-conductor / apps / worker-runner / src
im_wower  ·  2026-03-26

index.test.js

  1import assert from "node:assert/strict";
  2import { mkdir, mkdtemp, readFile, readdir, rm } from "node:fs/promises";
  3import { tmpdir } from "node:os";
  4import { join } from "node:path";
  5import test from "node:test";
  6
  7import {
  8  prepareStepRun,
  9  runStep
 10} from "../dist/index.js";
 11
 12async function createFixture(t) {
 13  const rootDir = await mkdtemp(join(tmpdir(), "baa-worker-runner-"));
 14  const repoRoot = join(rootDir, "repo");
 15  const worktreePath = join(rootDir, "worktrees", "task");
 16  const runsRootDir = join(rootDir, "runs");
 17
 18  await mkdir(worktreePath, { recursive: true });
 19  await mkdir(repoRoot, { recursive: true });
 20
 21  t.after(async () => {
 22    await rm(rootDir, { recursive: true, force: true });
 23  });
 24
 25  return {
 26    repoRoot,
 27    worktreePath,
 28    runsRootDir
 29  };
 30}
 31
 32function createRequest(runtime, overrides = {}) {
 33  return {
 34    taskId: "task-worker-runner",
 35    stepId: "step-001",
 36    runId: "run-001",
 37    attempt: 1,
 38    stepName: "Prepare local run",
 39    stepKind: "review",
 40    workerKind: "shell",
 41    timeoutSec: 45,
 42    runtime,
 43    createdAt: "2026-03-26T00:00:00.000Z",
 44    checkpoint: {
 45      mode: "capture",
 46      logTailLines: 10,
 47      summaryHint: "Prepared by worker-runner test."
 48    },
 49    ...overrides
 50  };
 51}
 52
 53test("prepareStepRun creates local run layout without checkpoints when disabled", async (t) => {
 54  const runtime = await createFixture(t);
 55  const run = await prepareStepRun(
 56    createRequest(runtime, {
 57      checkpoint: {
 58        mode: "disabled"
 59      }
 60    })
 61  );
 62
 63  assert.equal(run.metadata.checkpointMode, "disabled");
 64  assert.equal(run.state.status, "prepared");
 65  assert.equal(run.state.lastEventSeq, 1);
 66  assert.equal(run.checkpoint.mode, "disabled");
 67  assert.deepEqual(run.checkpoint.supportedTypes, []);
 68  assert.equal(run.checkpoint.records.length, 0);
 69  assert.deepEqual(
 70    run.logSession.worker.entries.map((entry) => entry.type),
 71    ["run_prepared"]
 72  );
 73
 74  const checkpointFiles = await readdir(run.logPaths.checkpointsDir);
 75  const workerLog = await readFile(run.logPaths.workerLogPath, "utf8");
 76
 77  assert.deepEqual(checkpointFiles, []);
 78  assert.match(workerLog, /"type":"run_prepared"/);
 79});
 80
 81test("runStep default placeholder executor persists capture checkpoints and lifecycle logs", async (t) => {
 82  const runtime = await createFixture(t);
 83  const result = await runStep(createRequest(runtime));
 84
 85  assert.equal(result.ok, true);
 86  assert.equal(result.outcome, "prepared");
 87  assert.equal(result.state.status, "prepared");
 88  assert.equal(result.metrics.checkpointCount, 2);
 89  assert.equal(result.checkpoint.records.length, 2);
 90  assert.equal(result.logSummary.lifecycleEventCount, 6);
 91  assert.equal(result.logSummary.stdoutChunkCount, 0);
 92  assert.equal(result.logSummary.stderrChunkCount, 0);
 93  assert.equal(result.state.checkpointSeq, 2);
 94  assert.deepEqual(
 95    result.lifecycleEvents.map((entry) => entry.type),
 96    [
 97      "run_prepared",
 98      "checkpoint_slot_reserved",
 99      "worker_started",
100      "worker_execution_deferred",
101      "worker_exited",
102      "step_prepared"
103    ]
104  );
105  assert.equal(result.checkpoint.records[0]?.type, "summary");
106  assert.equal(result.checkpoint.records[1]?.type, "log_tail");
107  assert.match(
108    result.checkpoint.records[1]?.contentText ?? "",
109    /\[worker\]/
110  );
111  assert.ok(result.artifacts.some((artifact) => artifact.name === "meta.json"));
112  assert.ok(result.artifacts.some((artifact) => artifact.name === "checkpoints"));
113
114  const checkpointFiles = await readdir(result.logPaths.checkpointsDir);
115  const workerLog = await readFile(result.logPaths.workerLogPath, "utf8");
116
117  assert.equal(checkpointFiles.length, 2);
118  assert.match(workerLog, /"type":"worker_execution_deferred"/);
119});
120
121test("runStep records blocked outcomes, stream logs, and follow-up metadata from a custom executor", async (t) => {
122  const runtime = await createFixture(t);
123  const result = await runStep(createRequest(runtime), {
124    async execute(run) {
125      return {
126        ok: false,
127        outcome: "blocked",
128        summary: `Manual approval required for ${run.request.stepId}.`,
129        blocked: true,
130        needsHuman: true,
131        stdout: ["stdout line"],
132        stderr: ["stderr line"],
133        suggestedFollowup: [
134          {
135            stepName: "Ask operator",
136            stepKind: "planner",
137            reason: "Approval required before continuing."
138          }
139        ],
140        artifacts: [
141          {
142            name: "handoff.txt",
143            kind: "artifact",
144            path: join(run.logPaths.artifactsDir, "handoff.txt"),
145            description: "Operator handoff note."
146          }
147        ],
148        exitCode: 17
149      };
150    }
151  });
152
153  assert.equal(result.ok, false);
154  assert.equal(result.outcome, "blocked");
155  assert.equal(result.blocked, true);
156  assert.equal(result.needsHuman, true);
157  assert.equal(result.state.status, "blocked");
158  assert.equal(result.state.exitCode, 17);
159  assert.equal(result.logSummary.stdoutChunkCount, 1);
160  assert.equal(result.logSummary.stderrChunkCount, 1);
161  assert.equal(result.lifecycleEvents.at(-1)?.type, "step_blocked");
162  assert.equal(result.suggestedFollowup.length, 1);
163  assert.equal(result.suggestedFollowup[0]?.stepKind, "planner");
164  assert.ok(result.artifacts.some((artifact) => artifact.name === "handoff.txt"));
165
166  const stdoutLog = await readFile(result.logPaths.stdoutLogPath, "utf8");
167  const stderrLog = await readFile(result.logPaths.stderrLogPath, "utf8");
168
169  assert.equal(stdoutLog, "stdout line\n");
170  assert.equal(stderrLog, "stderr line\n");
171});