mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 02:11:23 +00:00
refactor: dedupe gateway and scheduler test scaffolding
This commit is contained in:
@@ -18,6 +18,7 @@ const noopLogger = {
|
||||
trace: vi.fn(),
|
||||
};
|
||||
const TOP_OF_HOUR_STAGGER_MS = 5 * 60 * 1_000;
|
||||
type CronServiceOptions = ConstructorParameters<typeof CronService>[0];
|
||||
|
||||
function topOfHourOffsetMs(jobId: string) {
|
||||
const digest = crypto.createHash("sha256").update(jobId).digest();
|
||||
@@ -68,6 +69,40 @@ function createDueIsolatedJob(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function createDefaultIsolatedRunner(): CronServiceOptions["runIsolatedAgentJob"] {
|
||||
return vi.fn().mockResolvedValue({
|
||||
status: "ok",
|
||||
summary: "ok",
|
||||
}) as CronServiceOptions["runIsolatedAgentJob"];
|
||||
}
|
||||
|
||||
async function startCronForStore(params: {
|
||||
storePath: string;
|
||||
cronEnabled?: boolean;
|
||||
enqueueSystemEvent?: CronServiceOptions["enqueueSystemEvent"];
|
||||
requestHeartbeatNow?: CronServiceOptions["requestHeartbeatNow"];
|
||||
runIsolatedAgentJob?: CronServiceOptions["runIsolatedAgentJob"];
|
||||
onEvent?: CronServiceOptions["onEvent"];
|
||||
}) {
|
||||
const enqueueSystemEvent =
|
||||
params.enqueueSystemEvent ?? (vi.fn() as unknown as CronServiceOptions["enqueueSystemEvent"]);
|
||||
const requestHeartbeatNow =
|
||||
params.requestHeartbeatNow ?? (vi.fn() as unknown as CronServiceOptions["requestHeartbeatNow"]);
|
||||
const runIsolatedAgentJob = params.runIsolatedAgentJob ?? createDefaultIsolatedRunner();
|
||||
|
||||
const cron = new CronService({
|
||||
cronEnabled: params.cronEnabled ?? true,
|
||||
storePath: params.storePath,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent,
|
||||
requestHeartbeatNow,
|
||||
runIsolatedAgentJob,
|
||||
...(params.onEvent ? { onEvent: params.onEvent } : {}),
|
||||
});
|
||||
await cron.start();
|
||||
return cron;
|
||||
}
|
||||
|
||||
describe("Cron issue regressions", () => {
|
||||
beforeAll(async () => {
|
||||
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cron-issues-"));
|
||||
@@ -90,15 +125,10 @@ describe("Cron issue regressions", () => {
|
||||
it("covers schedule updates, force runs, isolated wake scheduling, and payload patching", async () => {
|
||||
const store = await makeStorePath();
|
||||
const enqueueSystemEvent = vi.fn();
|
||||
const cron = new CronService({
|
||||
cronEnabled: true,
|
||||
const cron = await startCronForStore({
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent,
|
||||
requestHeartbeatNow: vi.fn(),
|
||||
runIsolatedAgentJob: vi.fn().mockResolvedValue({ status: "ok", summary: "ok" }),
|
||||
});
|
||||
await cron.start();
|
||||
|
||||
const created = await cron.add({
|
||||
name: "hourly",
|
||||
@@ -171,15 +201,7 @@ describe("Cron issue regressions", () => {
|
||||
|
||||
it("repairs missing nextRunAtMs on non-schedule updates without touching other jobs", async () => {
|
||||
const store = await makeStorePath();
|
||||
const cron = new CronService({
|
||||
cronEnabled: true,
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent: vi.fn(),
|
||||
requestHeartbeatNow: vi.fn(),
|
||||
runIsolatedAgentJob: vi.fn().mockResolvedValue({ status: "ok", summary: "ok" }),
|
||||
});
|
||||
await cron.start();
|
||||
const cron = await startCronForStore({ storePath: store.storePath });
|
||||
|
||||
const created = await cron.add({
|
||||
name: "repair-target",
|
||||
@@ -205,15 +227,7 @@ describe("Cron issue regressions", () => {
|
||||
const store = await makeStorePath();
|
||||
const now = Date.parse("2026-02-06T10:05:00.000Z");
|
||||
vi.setSystemTime(now);
|
||||
const cron = new CronService({
|
||||
cronEnabled: false,
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent: vi.fn(),
|
||||
requestHeartbeatNow: vi.fn(),
|
||||
runIsolatedAgentJob: vi.fn().mockResolvedValue({ status: "ok", summary: "ok" }),
|
||||
});
|
||||
await cron.start();
|
||||
const cron = await startCronForStore({ storePath: store.storePath, cronEnabled: false });
|
||||
|
||||
const dueJob = await cron.add({
|
||||
name: "due-preserved",
|
||||
@@ -279,15 +293,7 @@ describe("Cron issue regressions", () => {
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const cron = new CronService({
|
||||
cronEnabled: true,
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent: vi.fn(),
|
||||
requestHeartbeatNow: vi.fn(),
|
||||
runIsolatedAgentJob: vi.fn().mockResolvedValue({ status: "ok", summary: "ok" }),
|
||||
});
|
||||
await cron.start();
|
||||
const cron = await startCronForStore({ storePath: store.storePath });
|
||||
|
||||
const listed = await cron.list();
|
||||
expect(listed.some((job) => job.id === "missing-enabled-update")).toBe(true);
|
||||
@@ -332,15 +338,11 @@ describe("Cron issue regressions", () => {
|
||||
);
|
||||
|
||||
const enqueueSystemEvent = vi.fn();
|
||||
const cron = new CronService({
|
||||
cronEnabled: false,
|
||||
const cron = await startCronForStore({
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
cronEnabled: false,
|
||||
enqueueSystemEvent,
|
||||
requestHeartbeatNow: vi.fn(),
|
||||
runIsolatedAgentJob: vi.fn().mockResolvedValue({ status: "ok", summary: "ok" }),
|
||||
});
|
||||
await cron.start();
|
||||
|
||||
const result = await cron.run("missing-enabled-due", "due");
|
||||
expect(result).toEqual({ ok: true, ran: true });
|
||||
@@ -355,15 +357,7 @@ describe("Cron issue regressions", () => {
|
||||
it("caps timer delay to 60s for far-future schedules", async () => {
|
||||
const timeoutSpy = vi.spyOn(globalThis, "setTimeout");
|
||||
const store = await makeStorePath();
|
||||
const cron = new CronService({
|
||||
cronEnabled: true,
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent: vi.fn(),
|
||||
requestHeartbeatNow: vi.fn(),
|
||||
runIsolatedAgentJob: vi.fn().mockResolvedValue({ status: "ok", summary: "ok" }),
|
||||
});
|
||||
await cron.start();
|
||||
const cron = await startCronForStore({ storePath: store.storePath });
|
||||
|
||||
const callsBeforeAdd = timeoutSpy.mock.calls.length;
|
||||
await cron.add({
|
||||
@@ -436,12 +430,8 @@ describe("Cron issue regressions", () => {
|
||||
const finished = createDeferred<void>();
|
||||
let targetJobId = "";
|
||||
|
||||
const cron = new CronService({
|
||||
cronEnabled: true,
|
||||
const cron = await startCronForStore({
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent: vi.fn(),
|
||||
requestHeartbeatNow: vi.fn(),
|
||||
runIsolatedAgentJob,
|
||||
onEvent: (evt: CronEvent) => {
|
||||
if (evt.jobId !== targetJobId) {
|
||||
@@ -454,7 +444,6 @@ describe("Cron issue regressions", () => {
|
||||
}
|
||||
},
|
||||
});
|
||||
await cron.start();
|
||||
|
||||
const runAt = Date.now() + 1;
|
||||
const job = await cron.add({
|
||||
@@ -525,16 +514,11 @@ describe("Cron issue regressions", () => {
|
||||
"utf-8",
|
||||
);
|
||||
const enqueueSystemEvent = vi.fn();
|
||||
const cron = new CronService({
|
||||
cronEnabled: true,
|
||||
const cron = await startCronForStore({
|
||||
storePath: store.storePath,
|
||||
log: noopLogger,
|
||||
enqueueSystemEvent,
|
||||
requestHeartbeatNow: vi.fn(),
|
||||
runIsolatedAgentJob: vi.fn().mockResolvedValue({ status: "ok" }),
|
||||
});
|
||||
|
||||
await cron.start();
|
||||
expect(enqueueSystemEvent).not.toHaveBeenCalled();
|
||||
cron.stop();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user