fix(cron): keep repaired schedule error state after force-run reload
This commit is contained in:
parent
dbc2925451
commit
44e912eccd
@ -397,6 +397,95 @@ describe("forceReload repairs externally changed cron schedules", () => {
|
||||
expect(persistedJob?.state?.lastStatus).toBe("ok");
|
||||
});
|
||||
|
||||
it("keeps scheduleErrorCount cleared when external reload fixes schedule during force-run", async () => {
|
||||
const store = await makeStorePath();
|
||||
let nowMs = Date.parse("2026-03-19T01:44:00.000Z");
|
||||
const jobId = "manual-run-reload-clears-schedule-error-count";
|
||||
const staleNextRunAtMs = Date.parse("2026-03-19T23:30:00.000Z");
|
||||
|
||||
const createJob = (params: {
|
||||
expr: string;
|
||||
scheduleErrorCount?: number;
|
||||
lastError?: string;
|
||||
nextRunAtMs?: number;
|
||||
}): CronJob => ({
|
||||
id: jobId,
|
||||
name: "manual run reload clears schedule error count",
|
||||
enabled: true,
|
||||
createdAtMs: Date.parse("2026-03-18T00:30:00.000Z"),
|
||||
updatedAtMs: Date.parse("2026-03-19T01:44:00.000Z"),
|
||||
schedule: { kind: "cron", expr: params.expr, tz: "Asia/Shanghai", staggerMs: 0 },
|
||||
sessionTarget: "isolated",
|
||||
wakeMode: "next-heartbeat",
|
||||
payload: { kind: "agentTurn", message: "tick" },
|
||||
state: {
|
||||
nextRunAtMs: params.nextRunAtMs,
|
||||
scheduleErrorCount: params.scheduleErrorCount,
|
||||
lastError: params.lastError,
|
||||
},
|
||||
});
|
||||
|
||||
await writeCronStoreSnapshot({
|
||||
storePath: store.storePath,
|
||||
jobs: [
|
||||
createJob({
|
||||
expr: "30 23 * * *",
|
||||
nextRunAtMs: staleNextRunAtMs,
|
||||
scheduleErrorCount: 2,
|
||||
lastError: "cron: invalid expression",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const runIsolatedAgentJob = vi.fn(async () => {
|
||||
await writeCronStoreSnapshot({
|
||||
storePath: store.storePath,
|
||||
jobs: [
|
||||
createJob({
|
||||
expr: "30 8 * * *",
|
||||
nextRunAtMs: staleNextRunAtMs,
|
||||
scheduleErrorCount: 2,
|
||||
lastError: "cron: invalid expression",
|
||||
}),
|
||||
],
|
||||
});
|
||||
nowMs += 500;
|
||||
return { status: "ok" as const, summary: "done" };
|
||||
});
|
||||
|
||||
const state = createCronServiceState({
|
||||
cronEnabled: true,
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
nowMs: () => nowMs,
|
||||
enqueueSystemEvent: vi.fn(),
|
||||
requestHeartbeatNow: vi.fn(),
|
||||
runIsolatedAgentJob,
|
||||
});
|
||||
|
||||
const result = await run(state, jobId, "force");
|
||||
expect(result).toEqual({ ok: true, ran: true });
|
||||
expect(runIsolatedAgentJob).toHaveBeenCalledTimes(1);
|
||||
|
||||
const merged = state.store?.jobs.find((job) => job.id === jobId);
|
||||
expect(merged?.schedule).toEqual({
|
||||
kind: "cron",
|
||||
expr: "30 8 * * *",
|
||||
tz: "Asia/Shanghai",
|
||||
staggerMs: 0,
|
||||
});
|
||||
expect(merged?.state.scheduleErrorCount).toBeUndefined();
|
||||
|
||||
const persisted = JSON.parse(await fs.readFile(store.storePath, "utf8")) as {
|
||||
jobs?: Array<{
|
||||
id: string;
|
||||
state?: { scheduleErrorCount?: number };
|
||||
}>;
|
||||
};
|
||||
const persistedJob = persisted.jobs?.find((job) => job.id === jobId);
|
||||
expect(persistedJob?.state?.scheduleErrorCount).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps one-shot terminal disable state when manual force-run reloads unchanged store", async () => {
|
||||
const store = await makeStorePath();
|
||||
let nowMs = Date.parse("2026-03-19T01:44:00.000Z");
|
||||
|
||||
@ -99,8 +99,8 @@ function mergeManualRunSnapshotAfterReload(params: {
|
||||
if (externalScheduleOrEnabledChanged) {
|
||||
reloaded.enabled = preservedEnabled;
|
||||
reloaded.state.nextRunAtMs = preservedNextRunAtMs;
|
||||
reloaded.state.scheduleErrorCount = preservedScheduleErrorCount;
|
||||
if (preservedScheduleErrorCount !== undefined) {
|
||||
reloaded.state.scheduleErrorCount = preservedScheduleErrorCount;
|
||||
reloaded.state.lastError = preservedScheduleErrorText;
|
||||
}
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user