import { describe, expect, it, beforeEach } from "@jest/globals"; import { createHeartbeatTestHarness, makeMonitor, type HeartbeatTestHarness } from "../helpers/heartbeatTestHarness.ts"; import type { MaintenanceWindow } from "../../src/domain/maintenance-windows/maintenance-window.type.ts"; const makeMaintenanceWindow = (overrides?: Partial): MaintenanceWindow => { const now = new Date(); const start = new Date(now.getTime() - 60 * 60 * 1000); // 1 hour ago const end = new Date(now.getTime() + 60 * 60 * 1000); // 1 hour from now return { id: "mw-1", monitorIds: ["mon-1"], teamId: "team-1", active: true, name: "Test Maintenance", duration: 2, durationUnit: "hours", repeat: 0, start: start.toISOString(), end: end.toISOString(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), ...overrides, }; }; describe("Heartbeat job: maintenance windows", () => { let h: HeartbeatTestHarness; beforeEach(() => { h = createHeartbeatTestHarness(); }); it("skips checks and sets monitor to maintenance when window is active", async () => { const monitor = makeMonitor(); h.monitorsRepo.seed(monitor); // Configure an active maintenance window covering the current time h.maintenanceWindowsRepo.findByMonitorId.mockResolvedValue([makeMaintenanceWindow()]); await h.heartbeatJob(monitor); // Monitor status should be set to maintenance const storedMonitor = await h.monitorsRepo.findById("mon-1", "team-1"); expect(storedMonitor.status).toBe("maintenance"); // No network request should have been made expect(h.providerRegistry.probe).not.toHaveBeenCalled(); // No check should have been buffered expect(h.bufferStub.addToBuffer).not.toHaveBeenCalled(); // No incident should have been created expect(h.incidentsRepo.getAll()).toHaveLength(0); // No notification should have been sent expect(h.notificationsService.handleNotifications).not.toHaveBeenCalled(); }); it("does not update status if monitor is already in maintenance", async () => { const monitor = makeMonitor({ status: "maintenance" }); h.monitorsRepo.seed(monitor); h.maintenanceWindowsRepo.findByMonitorId.mockResolvedValue([makeMaintenanceWindow()]); await h.heartbeatJob(monitor); // findById reads the stored monitor; updateById should not have been called // since status was already "maintenance" const storedMonitor = await h.monitorsRepo.findById("mon-1", "team-1"); expect(storedMonitor.status).toBe("maintenance"); expect(h.providerRegistry.probe).not.toHaveBeenCalled(); }); it("resumes normal checks when maintenance window expires", async () => { const monitor = makeMonitor(); h.monitorsRepo.seed(monitor); // First heartbeat: inside maintenance window h.maintenanceWindowsRepo.findByMonitorId.mockResolvedValue([makeMaintenanceWindow()]); await h.heartbeatJob(monitor); let storedMonitor = await h.monitorsRepo.findById("mon-1", "team-1"); expect(storedMonitor.status).toBe("maintenance"); // Second heartbeat: maintenance window expired (no windows returned) h.maintenanceWindowsRepo.findByMonitorId.mockResolvedValue([]); h.setNextResponse(true, 200); await h.heartbeatJob(monitor); // Network request should have been made this time expect(h.providerRegistry.probe).toHaveBeenCalledTimes(1); // Check should have been buffered expect(h.bufferStub.addToBuffer).toHaveBeenCalledTimes(1); }); it("skips checks when window is inactive (active: false)", async () => { const monitor = makeMonitor(); h.monitorsRepo.seed(monitor); // Inactive maintenance window — should be ignored h.maintenanceWindowsRepo.findByMonitorId.mockResolvedValue([makeMaintenanceWindow({ active: false })]); h.setNextResponse(true, 200); await h.heartbeatJob(monitor); // Normal check should proceed since window is inactive expect(h.providerRegistry.probe).toHaveBeenCalledTimes(1); expect(h.bufferStub.addToBuffer).toHaveBeenCalledTimes(1); }); it("skips checks for every monitor covered by a group window", async () => { const monitorA = makeMonitor({ id: "mon-a", name: "Monitor A" }); const monitorB = makeMonitor({ id: "mon-b", name: "Monitor B" }); h.monitorsRepo.seed(monitorA); h.monitorsRepo.seed(monitorB); // A single group window covers both monitors h.maintenanceWindowsRepo.findByMonitorId.mockResolvedValue([makeMaintenanceWindow({ monitorIds: ["mon-a", "mon-b"] })]); await h.heartbeatJob(monitorA); await h.heartbeatJob(monitorB); expect((await h.monitorsRepo.findById("mon-a", "team-1")).status).toBe("maintenance"); expect((await h.monitorsRepo.findById("mon-b", "team-1")).status).toBe("maintenance"); expect(h.providerRegistry.probe).not.toHaveBeenCalled(); }); it("skips checks when window is in the past (expired)", async () => { const monitor = makeMonitor(); h.monitorsRepo.seed(monitor); // Window ended an hour ago const pastEnd = new Date(Date.now() - 60 * 60 * 1000); const pastStart = new Date(pastEnd.getTime() - 2 * 60 * 60 * 1000); h.maintenanceWindowsRepo.findByMonitorId.mockResolvedValue([ makeMaintenanceWindow({ start: pastStart.toISOString(), end: pastEnd.toISOString() }), ]); h.setNextResponse(true, 200); await h.heartbeatJob(monitor); // Window is in the past — normal check should proceed expect(h.providerRegistry.probe).toHaveBeenCalledTimes(1); }); });