From cb218a46a9b21e6382a92c39d2fe2871737d3c51 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 4 May 2026 10:21:28 -0700 Subject: [PATCH 1/2] fix pause/resume logic --- .../infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts index 7260c82c7..d64a8932b 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts @@ -224,7 +224,7 @@ export class SuperSimpleQueue implements ISuperSimpleQueue { throw new Error("Failed to pause monitor"); } const geoJob = await this.scheduler.getJob(`${monitor.id}-geo`); - if (geoJob) await this.scheduler.removeJob(`${monitor.id}-geo`); + if (geoJob) await this.scheduler.pauseJob(`${monitor.id}-geo`); this.logger.debug({ message: `Paused monitor ${monitor.id}`, @@ -239,8 +239,10 @@ export class SuperSimpleQueue implements ISuperSimpleQueue { throw new Error("Failed to resume monitor"); } - await this.scheduler.resumeJob(`${monitor.id}-geo`); - + const geoJob = await this.scheduler.getJob(`${monitor.id}-geo`); + if (geoJob) { + await this.scheduler.resumeJob(`${monitor.id}-geo`); + } this.logger.debug({ message: `Resumed monitor ${monitor.id}`, service: SERVICE_NAME, From 6f0cc307dc85e7ce5250a2001e7fc56c9d2bb3cf Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 4 May 2026 10:51:53 -0700 Subject: [PATCH 2/2] log error on geo check pause/resume fail, update tests --- .../SuperSimpleQueue/SuperSimpleQueue.ts | 20 ++++++- .../unit/services/superSimpleQueue.test.ts | 54 ++++++++++++++++--- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts index d64a8932b..1d50d720c 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts @@ -224,7 +224,16 @@ export class SuperSimpleQueue implements ISuperSimpleQueue { throw new Error("Failed to pause monitor"); } const geoJob = await this.scheduler.getJob(`${monitor.id}-geo`); - if (geoJob) await this.scheduler.pauseJob(`${monitor.id}-geo`); + if (geoJob) { + const geoRes = await this.scheduler.pauseJob(`${monitor.id}-geo`); + if (geoRes === false) { + this.logger.error({ + message: `Failed to pause geo check job for monitor ${monitor.id}`, + service: SERVICE_NAME, + method: "pauseJob", + }); + } + } this.logger.debug({ message: `Paused monitor ${monitor.id}`, @@ -241,7 +250,14 @@ export class SuperSimpleQueue implements ISuperSimpleQueue { const geoJob = await this.scheduler.getJob(`${monitor.id}-geo`); if (geoJob) { - await this.scheduler.resumeJob(`${monitor.id}-geo`); + const geoRes = await this.scheduler.resumeJob(`${monitor.id}-geo`); + if (geoRes === false) { + this.logger.error({ + message: `Failed to resume geo check job for monitor ${monitor.id}`, + service: SERVICE_NAME, + method: "resumeJob", + }); + } } this.logger.debug({ message: `Resumed monitor ${monitor.id}`, diff --git a/server/test/unit/services/superSimpleQueue.test.ts b/server/test/unit/services/superSimpleQueue.test.ts index 93563dd00..95367cb5a 100644 --- a/server/test/unit/services/superSimpleQueue.test.ts +++ b/server/test/unit/services/superSimpleQueue.test.ts @@ -291,34 +291,46 @@ describe("SuperSimpleQueue", () => { // ── pauseJob ───────────────────────────────────────────────────────────── describe("pauseJob", () => { - it("pauses monitor and removes geo job if it exists", async () => { + it("pauses monitor and pauses geo job when geo exists", async () => { const { queue, scheduler, logger } = createQueue(); (scheduler.getJob as jest.Mock).mockResolvedValue({ id: "mon-1-geo" }); await queue.pauseJob(makeMonitor()); expect(scheduler.pauseJob).toHaveBeenCalledWith("mon-1"); - expect(scheduler.removeJob).toHaveBeenCalledWith("mon-1-geo"); + expect(scheduler.pauseJob).toHaveBeenCalledWith("mon-1-geo"); + expect(scheduler.removeJob).not.toHaveBeenCalledWith("mon-1-geo"); expect(logger.debug).toHaveBeenCalledWith(expect.objectContaining({ message: "Paused monitor mon-1" })); }); - it("does not remove geo job if it does not exist", async () => { + it("does not pause geo job when it does not exist", async () => { const { queue, scheduler } = createQueue(); await queue.pauseJob(makeMonitor()); - expect(scheduler.removeJob).not.toHaveBeenCalledWith("mon-1-geo"); + expect(scheduler.pauseJob).toHaveBeenCalledWith("mon-1"); + expect(scheduler.pauseJob).not.toHaveBeenCalledWith("mon-1-geo"); }); - it("throws when scheduler.pauseJob returns false", async () => { + it("throws when scheduler.pauseJob returns false for the main job", async () => { const { queue, scheduler } = createQueue(); (scheduler.pauseJob as jest.Mock).mockResolvedValue(false); await expect(queue.pauseJob(makeMonitor())).rejects.toThrow("Failed to pause monitor"); }); + + it("logs error but does not throw when geo pauseJob returns false", async () => { + const { queue, scheduler, logger } = createQueue(); + (scheduler.getJob as jest.Mock).mockResolvedValue({ id: "mon-1-geo" }); + (scheduler.pauseJob as jest.Mock).mockImplementation(async (id: string) => id !== "mon-1-geo"); + + await expect(queue.pauseJob(makeMonitor())).resolves.toBeUndefined(); + expect(logger.error).toHaveBeenCalledWith(expect.objectContaining({ message: "Failed to pause geo check job for monitor mon-1" })); + }); }); // ── resumeJob ──────────────────────────────────────────────────────────── describe("resumeJob", () => { - it("resumes monitor and geo job", async () => { + it("resumes monitor and geo job when geo exists", async () => { const { queue, scheduler, logger } = createQueue(); + (scheduler.getJob as jest.Mock).mockResolvedValue({ id: "mon-1-geo" }); await queue.resumeJob(makeMonitor()); expect(scheduler.resumeJob).toHaveBeenCalledWith("mon-1"); @@ -326,11 +338,39 @@ describe("SuperSimpleQueue", () => { expect(logger.debug).toHaveBeenCalledWith(expect.objectContaining({ message: "Resumed monitor mon-1" })); }); - it("throws when scheduler.resumeJob returns false", async () => { + it("does not resume geo job when it does not exist", async () => { + const { queue, scheduler } = createQueue(); + await queue.resumeJob(makeMonitor()); + expect(scheduler.resumeJob).toHaveBeenCalledWith("mon-1"); + expect(scheduler.resumeJob).not.toHaveBeenCalledWith("mon-1-geo"); + }); + + it("throws when scheduler.resumeJob returns false for the main job", async () => { const { queue, scheduler } = createQueue(); (scheduler.resumeJob as jest.Mock).mockResolvedValue(false); await expect(queue.resumeJob(makeMonitor())).rejects.toThrow("Failed to resume monitor"); }); + + it("logs error but does not throw when geo resumeJob returns false", async () => { + const { queue, scheduler, logger } = createQueue(); + (scheduler.getJob as jest.Mock).mockResolvedValue({ id: "mon-1-geo" }); + (scheduler.resumeJob as jest.Mock).mockImplementation(async (id: string) => id !== "mon-1-geo"); + + await expect(queue.resumeJob(makeMonitor())).resolves.toBeUndefined(); + expect(logger.error).toHaveBeenCalledWith(expect.objectContaining({ message: "Failed to resume geo check job for monitor mon-1" })); + }); + + it("preserves geo job through a pause/resume cycle", async () => { + const { queue, scheduler } = createQueue(); + (scheduler.getJob as jest.Mock).mockResolvedValue({ id: "mon-1-geo" }); + + await queue.pauseJob(makeMonitor()); + await queue.resumeJob(makeMonitor()); + + expect(scheduler.removeJob).not.toHaveBeenCalledWith("mon-1-geo"); + expect(scheduler.pauseJob).toHaveBeenCalledWith("mon-1-geo"); + expect(scheduler.resumeJob).toHaveBeenCalledWith("mon-1-geo"); + }); }); // ── updateJob ────────────────────────────────────────────────────────────