import { afterEach, describe, expect, it, vi } from "vitest"; import type { ClawdbotConfig } from "../config/config.js"; import { startHeartbeatRunner } from "./heartbeat-runner.js"; describe("startHeartbeatRunner", () => { afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); }); it("updates scheduling when config changes without restart", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date(0)); const runSpy = vi.fn().mockResolvedValue({ status: "ran", durationMs: 1 }); const runner = startHeartbeatRunner({ cfg: { agents: { defaults: { heartbeat: { every: "30m" } } }, } as ClawdbotConfig, runOnce: runSpy, }); await vi.advanceTimersByTimeAsync(30 * 60_000 + 1_000); expect(runSpy).toHaveBeenCalledTimes(1); expect(runSpy.mock.calls[0]?.[0]).toEqual( expect.objectContaining({ agentId: "main", reason: "interval" }), ); runner.updateConfig({ agents: { defaults: { heartbeat: { every: "30m" } }, list: [ { id: "main", heartbeat: { every: "10m" } }, { id: "ops", heartbeat: { every: "15m" } }, ], }, } as ClawdbotConfig); await vi.advanceTimersByTimeAsync(10 * 60_000 + 1_000); expect(runSpy).toHaveBeenCalledTimes(2); expect(runSpy.mock.calls[1]?.[0]).toEqual( expect.objectContaining({ agentId: "main", heartbeat: { every: "10m" } }), ); await vi.advanceTimersByTimeAsync(5 * 60_000 + 1_000); expect(runSpy).toHaveBeenCalledTimes(3); expect(runSpy.mock.calls[2]?.[0]).toEqual( expect.objectContaining({ agentId: "ops", heartbeat: { every: "15m" } }), ); runner.stop(); }); });