test/loop-durable-project-boundary.test.js
Rama main · réplica de lectura del repositorio original.
var test = require("node:test");
var assert = require("node:assert/strict");
var fs = require("fs");
var os = require("os");
var path = require("path");
var attachScheduler = require("../lib/durable-scheduler").attachDurableScheduler;
var attachLoop = require("../lib/project-loop").attachLoop;
function turn() { return new Promise(function (resolve) { setImmediate(resolve); }); }
function fixture(t) {
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "clay-loop-project-boundary-")); var now = { value: 1000 }; var events = []; var sdkCalls = 0;
var scheduler = attachScheduler({ storageDir: dir, namespace: "test", now: function () { return now.value; }, setTimer: function () { return { unref: function () {} }; }, clearTimer: function () {} });
var sessions = new Map();
var sm = { sessions: sessions, setResolveLoopInfo: function () {}, saveSessionFile: function () {}, broadcastSessionList: function () {}, appendToSessionFile: function () {},
createSession: function (input) { var session = { localId: sessions.size + 1, ownerId: input.ownerId || null, history: [] }; sessions.set(session.localId, session); return session; } };
var loop = attachLoop({
cwd: dir, slug: "project-boundary", sm: sm, durableScheduler: scheduler, loopRegistryPath: path.join(dir, "registry.jsonl"), loopStatePath: path.join(dir, "loop-state.json"),
sdk: { startQuery: function () { sdkCalls++; return Promise.resolve(); } }, send: function (message) { events.push(message); }, sendTo: function () {}, sendToSession: function () {},
getHubSchedules: function () { return []; }, getLinuxUserForSession: function () { return null; }, onProcessingChanged: function () {}, hydrateImageRefs: function (value) { return value; },
isMultiUser: function () { return false; }, authorizeScheduledRun: function () { return true; }, canExecuteScheduledTask: function () { return true; },
prepareScheduledTask: function () { return { ok: true, activeRun: { runId: "reserved", status: "starting", startedAt: now.value } }; },
executeScheduledTask: function () { return { ok: false, error: "the pair executor refused this run" }; },
});
t.after(async function () { loop.stopTimer(); await scheduler.shutdown(); fs.rmSync(dir, { recursive: true, force: true }); });
return { dir: dir, now: now, scheduler: scheduler, loop: loop, events: events, sdkCalls: function () { return sdkCalls; } };
}
test("durable project dispatch records pair refusal and missing legacy prompt as failures", async function (t) {
var f = fixture(t);
var pairRecord = f.loop.loopRegistry.register({ id: "refused-pair", name: "Refused pair", task: "Run", cron: "* * * * *", ownerId: null, source: "schedule", createdViaScheduledTasks: true,
execution: { driver: { vendor: "claude" }, worker: { vendor: "codex" } } });
f.loop.loopRegistry.updateRecord(pairRecord.id, { nextRunAt: f.now.value });
var missingPrompt = f.loop.loopRegistry.register({ id: "missing-prompt", name: "Missing prompt", task: "Run", cron: "* * * * *", ownerId: null });
f.loop.loopRegistry.updateRecord(missingPrompt.id, { nextRunAt: f.now.value });
f.scheduler.start(); await turn(); await turn();
var jobs = f.scheduler.listJobs();
var refusedJob = jobs.find(function (job) { return job.target.id === pairRecord.id && job.payload.occurrenceAt === f.now.value; });
var missingJob = jobs.find(function (job) { return job.target.id === missingPrompt.id && job.payload.occurrenceAt === f.now.value; });
assert.equal(refusedJob.state, "failed"); assert.match(refusedJob.execution.error, /pair executor refused/);
assert.equal(missingJob.state, "failed"); assert.match(missingJob.execution.error, /PROMPT\.md is missing/);
assert.equal(f.events.filter(function (event) { return event.type === "loop_error"; }).length, 2); assert.equal(f.sdkCalls(), 0);
assert.equal(jobs.filter(function (job) { return job.target.id === pairRecord.id && job.payload.occurrenceAt === f.now.value; }).length, 1);
assert.equal(jobs.filter(function (job) { return job.target.id === missingPrompt.id && job.payload.occurrenceAt === f.now.value; }).length, 1);
});