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});