test(perf): merge duplicate one-shot retry regression paths

This commit is contained in:
Peter Steinberger
2026-03-02 15:23:50 +00:00
parent 44c50d9a73
commit a905b6dabc

View File

@@ -724,50 +724,76 @@ describe("Cron issue regressions", () => {
}
});
it("#24355: one-shot job retries on transient error, then succeeds", async () => {
const store = await makeStorePath();
it("#24355: one-shot retries then succeeds (with and without deleteAfterRun)", async () => {
const scheduledAt = Date.parse("2026-02-06T10:00:00.000Z");
const cronJob = createIsolatedRegressionJob({
const runRetryScenario = async (params: {
id: string;
deleteAfterRun: boolean;
}): Promise<{
state: ReturnType<typeof createCronServiceState>;
runIsolatedAgentJob: ReturnType<typeof vi.fn>;
firstRetryAtMs: number;
}> => {
const store = await makeStorePath();
const cronJob = createIsolatedRegressionJob({
id: params.id,
name: "reminder",
scheduledAt,
schedule: { kind: "at", at: new Date(scheduledAt).toISOString() },
payload: { kind: "agentTurn", message: "remind me" },
state: { nextRunAtMs: scheduledAt },
});
cronJob.deleteAfterRun = params.deleteAfterRun;
await writeCronJobs(store.storePath, [cronJob]);
let now = scheduledAt;
const runIsolatedAgentJob = vi
.fn()
.mockResolvedValueOnce({ status: "error", error: "429 rate limit exceeded" })
.mockResolvedValueOnce({ status: "ok", summary: "done" });
const state = createCronServiceState({
cronEnabled: true,
storePath: store.storePath,
log: noopLogger,
nowMs: () => now,
enqueueSystemEvent: vi.fn(),
requestHeartbeatNow: vi.fn(),
runIsolatedAgentJob,
});
await onTimer(state);
const jobAfterRetry = state.store?.jobs.find((j) => j.id === params.id);
expect(jobAfterRetry).toBeDefined();
expect(jobAfterRetry!.enabled).toBe(true);
expect(jobAfterRetry!.state.lastStatus).toBe("error");
expect(jobAfterRetry!.state.nextRunAtMs).toBeDefined();
expect(jobAfterRetry!.state.nextRunAtMs).toBeGreaterThan(scheduledAt);
const firstRetryAtMs = (jobAfterRetry!.state.nextRunAtMs ?? 0) + 1;
now = firstRetryAtMs;
await onTimer(state);
return { state, runIsolatedAgentJob, firstRetryAtMs };
};
const keepResult = await runRetryScenario({
id: "oneshot-retry",
name: "reminder",
scheduledAt,
schedule: { kind: "at", at: new Date(scheduledAt).toISOString() },
payload: { kind: "agentTurn", message: "remind me" },
state: { nextRunAtMs: scheduledAt },
deleteAfterRun: false,
});
cronJob.deleteAfterRun = false;
await writeCronJobs(store.storePath, [cronJob]);
const keepJob = keepResult.state.store?.jobs.find((j) => j.id === "oneshot-retry");
expect(keepJob).toBeDefined();
expect(keepJob!.state.lastStatus).toBe("ok");
expect(keepResult.runIsolatedAgentJob).toHaveBeenCalledTimes(2);
let now = scheduledAt;
const runIsolatedAgentJob = vi
.fn()
.mockResolvedValueOnce({ status: "error", error: "429 rate limit exceeded" })
.mockResolvedValueOnce({ status: "ok", summary: "done" });
const state = createCronServiceState({
cronEnabled: true,
storePath: store.storePath,
log: noopLogger,
nowMs: () => now,
enqueueSystemEvent: vi.fn(),
requestHeartbeatNow: vi.fn(),
runIsolatedAgentJob,
const deleteResult = await runRetryScenario({
id: "oneshot-deleteAfterRun-retry",
deleteAfterRun: true,
});
await onTimer(state);
let job = state.store?.jobs.find((j) => j.id === "oneshot-retry");
expect(job).toBeDefined();
expect(job!.enabled).toBe(true);
expect(job!.state.lastStatus).toBe("error");
expect(job!.state.nextRunAtMs).toBeDefined();
expect(job!.state.nextRunAtMs).toBeGreaterThan(scheduledAt);
now = (job!.state.nextRunAtMs ?? 0) + 1;
await onTimer(state);
job = state.store?.jobs.find((j) => j.id === "oneshot-retry");
expect(job).toBeDefined();
expect(job!.state.lastStatus).toBe("ok");
expect(runIsolatedAgentJob).toHaveBeenCalledTimes(2);
const deletedJob = deleteResult.state.store?.jobs.find(
(j) => j.id === "oneshot-deleteAfterRun-retry",
);
expect(deletedJob).toBeUndefined();
expect(deleteResult.runIsolatedAgentJob).toHaveBeenCalledTimes(2);
});
it("#24355: one-shot job disabled after max transient retries", async () => {
@@ -896,52 +922,6 @@ describe("Cron issue regressions", () => {
expect(job!.state.nextRunAtMs).toBeUndefined();
});
it("#24355: deleteAfterRun:true one-shot job is deleted after successful retry", async () => {
const store = await makeStorePath();
const scheduledAt = Date.parse("2026-02-06T10:00:00.000Z");
const cronJob = createIsolatedRegressionJob({
id: "oneshot-deleteAfterRun-retry",
name: "reminder",
scheduledAt,
schedule: { kind: "at", at: new Date(scheduledAt).toISOString() },
payload: { kind: "agentTurn", message: "remind me" },
state: { nextRunAtMs: scheduledAt },
});
cronJob.deleteAfterRun = true;
await writeCronJobs(store.storePath, [cronJob]);
let now = scheduledAt;
const runIsolatedAgentJob = vi
.fn()
.mockResolvedValueOnce({ status: "error", error: "429 rate limit exceeded" })
.mockResolvedValueOnce({ status: "ok", summary: "done" });
const state = createCronServiceState({
cronEnabled: true,
storePath: store.storePath,
log: noopLogger,
nowMs: () => now,
enqueueSystemEvent: vi.fn(),
requestHeartbeatNow: vi.fn(),
runIsolatedAgentJob,
});
// First run: transient error → retry scheduled, job still in store.
await onTimer(state);
let job = state.store?.jobs.find((j) => j.id === "oneshot-deleteAfterRun-retry");
expect(job).toBeDefined();
expect(job!.enabled).toBe(true);
expect(job!.state.lastStatus).toBe("error");
expect(job!.state.nextRunAtMs).toBeGreaterThan(scheduledAt);
// Second run: success → deleteAfterRun removes the job from the store.
now = (job!.state.nextRunAtMs ?? 0) + 1;
await onTimer(state);
const deleted = state.store?.jobs.find((j) => j.id === "oneshot-deleteAfterRun-retry");
expect(deleted).toBeUndefined();
expect(runIsolatedAgentJob).toHaveBeenCalledTimes(2);
});
it("prevents spin loop when cron job completes within the scheduled second (#17821)", async () => {
const store = await makeStorePath();
// Simulate a cron job "0 13 * * *" (daily 13:00 UTC) that fires exactly