refactor: dedupe gateway and scheduler test scaffolding

This commit is contained in:
Peter Steinberger
2026-02-18 04:03:34 +00:00
parent 262472ba20
commit adac9cb67f
5 changed files with 334 additions and 544 deletions

View File

@@ -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();
}