import { describe, expect, it } from "vitest"; import { nextRecoveryCheck, RecoverySchedule } from "./recovery-schedule"; describe("provider recovery backoff", () => { it("keeps worker generations increasing past 100 while capping only backoff", () => { const first = nextRecoveryCheck("run", "revision", undefined, 0)!; const next = RecoverySchedule.parse(nextRecoveryCheck("run", "revision", { ...first, attempt: 100 }, 1000)); expect(next.attempt).toBe(101); expect(Date.parse(next.nextCheckAt) - 1000).toBe(60 * 60_000); expect(nextRecoveryCheck("run", "revision", { ...first, attempt: 2147483647 }, 1000)).toBeUndefined(); }); it("waits 30 minutes, increases by 1.3, and caps intervals at an hour", () => { let now = Date.parse("2026-09-16T00:00:00.000Z"); let previous: RecoverySchedule | undefined; for (const minutes of [30, 39, 50.7, 60, 60]) { const next = RecoverySchedule.parse(nextRecoveryCheck("run", "revision", previous, now)); expect(Date.parse(next.nextCheckAt) - now).toBe(minutes * 60_000); expect(next.deadline).toBe("2026-09-18T00:00:00.000Z"); now = Date.parse(next.nextCheckAt); previous = next; } }); it("schedules a final check at the deadline and never extends the 48-hour window", () => { const first = nextRecoveryCheck("run", "revision", undefined, 0)!; const deadline = Date.parse(first.deadline); const last = nextRecoveryCheck("run", "revision", first, deadline - 1000)!; expect(last.nextCheckAt).toBe(first.deadline); expect(nextRecoveryCheck("run", "revision", last, deadline)).toBeUndefined(); expect(nextRecoveryCheck("run", "revision", last, deadline + 1)).toBeUndefined(); }); it("slows to two hours after day one and upgrades legacy schedules only once", () => { const day = 24 * 60 * 60_000; const legacy = { runId: "run", revision: "rev", attempt: 25, deadline: new Date(day).toISOString(), nextCheckAt: new Date(day).toISOString(), }; const next = nextRecoveryCheck("run", "rev", legacy, day)!; expect(next.nextCheckAt).toBe(new Date(day + 2 * 60 * 60_000).toISOString()); expect(next.deadline).toBe(new Date(2 * day).toISOString()); expect(nextRecoveryCheck("run", "rev", next, 2 * day)).toBeUndefined(); }); });