diff --git a/src/cron/service/ops.test.ts b/src/cron/service/ops.test.ts index 69b9f1f0dc4..5e1ba391100 100644 --- a/src/cron/service/ops.test.ts +++ b/src/cron/service/ops.test.ts @@ -41,6 +41,21 @@ function createTimedOutIsolatedCronState(params: { storePath: string; now: numbe }); } +function createOkIsolatedCronState(params: { storePath: string; now: number; summary?: string }) { + return createCronServiceState({ + storePath: params.storePath, + cronEnabled: true, + log: logger, + nowMs: () => params.now, + enqueueSystemEvent: vi.fn(), + requestHeartbeatNow: vi.fn(), + runIsolatedAgentJob: vi.fn(async () => ({ + status: "ok" as const, + ...(params.summary === undefined ? {} : { summary: params.summary }), + })), + }); +} + function createInterruptedMainJob(now: number): CronJob { return { id: "startup-interrupted", @@ -75,6 +90,25 @@ function createDueIsolatedJob(now: number): CronJob { }; } +async function writeDueIsolatedJobSnapshot(storePath: string, now: number) { + await writeCronStoreSnapshot({ + storePath, + jobs: [createDueIsolatedJob(now)], + }); +} + +async function expectDueIsolatedManualRunProgresses(storePath: string, now: number) { + const state = createOkIsolatedCronState({ storePath, now, summary: "done" }); + + await expect(run(state, "isolated-timeout")).resolves.toEqual({ ok: true, ran: true }); + + const persisted = JSON.parse(await fs.readFile(storePath, "utf8")) as { + jobs: CronJob[]; + }; + expect(persisted.jobs[0]?.state.runningAtMs).toBeUndefined(); + expect(persisted.jobs[0]?.state.lastStatus).toBe("ok"); +} + function createMissedIsolatedJob(now: number): CronJob { return { id: "startup-timeout", @@ -150,10 +184,7 @@ describe("cron service ops seam coverage", () => { const now = Date.parse("2026-03-23T12:00:00.000Z"); const restoreStateDir = withStateDirForStorePath(storePath); - await writeCronStoreSnapshot({ - storePath, - jobs: [createDueIsolatedJob(now)], - }); + await writeDueIsolatedJobSnapshot(storePath, now); const state = createTimedOutIsolatedCronState({ storePath, @@ -186,23 +217,7 @@ describe("cron service ops seam coverage", () => { throw new Error("disk full"); }); - const state = createCronServiceState({ - storePath, - cronEnabled: true, - log: logger, - nowMs: () => now, - enqueueSystemEvent: vi.fn(), - requestHeartbeatNow: vi.fn(), - runIsolatedAgentJob: vi.fn(async () => ({ status: "ok" as const, summary: "done" })), - }); - - await expect(run(state, "isolated-timeout")).resolves.toEqual({ ok: true, ran: true }); - - const persisted = JSON.parse(await fs.readFile(storePath, "utf8")) as { - jobs: CronJob[]; - }; - expect(persisted.jobs[0]?.state.runningAtMs).toBeUndefined(); - expect(persisted.jobs[0]?.state.lastStatus).toBe("ok"); + await expectDueIsolatedManualRunProgresses(storePath, now); expect(logger.warn).toHaveBeenCalledWith( expect.objectContaining({ jobId: "isolated-timeout" }), "cron: failed to create task ledger record", @@ -219,10 +234,7 @@ describe("cron service ops seam coverage", () => { process.env.OPENCLAW_STATE_DIR = stateRoot; resetTaskRegistryForTests(); - await writeCronStoreSnapshot({ - storePath, - jobs: [createDueIsolatedJob(now)], - }); + await writeDueIsolatedJobSnapshot(storePath, now); const updateTaskRecordSpy = vi .spyOn(taskExecutor, "completeTaskRunByRunId") @@ -230,23 +242,7 @@ describe("cron service ops seam coverage", () => { throw new Error("disk full"); }); - const state = createCronServiceState({ - storePath, - cronEnabled: true, - log: logger, - nowMs: () => now, - enqueueSystemEvent: vi.fn(), - requestHeartbeatNow: vi.fn(), - runIsolatedAgentJob: vi.fn(async () => ({ status: "ok" as const, summary: "done" })), - }); - - await expect(run(state, "isolated-timeout")).resolves.toEqual({ ok: true, ran: true }); - - const persisted = JSON.parse(await fs.readFile(storePath, "utf8")) as { - jobs: CronJob[]; - }; - expect(persisted.jobs[0]?.state.runningAtMs).toBeUndefined(); - expect(persisted.jobs[0]?.state.lastStatus).toBe("ok"); + await expectDueIsolatedManualRunProgresses(storePath, now); expect(logger.warn).toHaveBeenCalledWith( expect.objectContaining({ jobStatus: "ok" }), "cron: failed to update task ledger record", @@ -284,15 +280,7 @@ describe("cron service ops seam coverage", () => { ], }); - const state = createCronServiceState({ - storePath, - cronEnabled: true, - log: logger, - nowMs: () => now, - enqueueSystemEvent: vi.fn(), - requestHeartbeatNow: vi.fn(), - runIsolatedAgentJob: vi.fn(async () => ({ status: "ok" as const })), - }); + const state = createOkIsolatedCronState({ storePath, now }); const updated = await update(state, "daily-report", { description: "edited" }); @@ -322,15 +310,7 @@ describe("cron service ops seam coverage", () => { ], }); - const state = createCronServiceState({ - storePath, - cronEnabled: true, - log: logger, - nowMs: () => now, - enqueueSystemEvent: vi.fn(), - requestHeartbeatNow: vi.fn(), - runIsolatedAgentJob: vi.fn(async () => ({ status: "ok" as const })), - }); + const state = createOkIsolatedCronState({ storePath, now }); const updated = await update(state, "broken-job", { description: "fixed" });