update tests, add check producer tests

This commit is contained in:
Alex Holliday committed 2026-06-18 11:06:57 -07:00
1 parent e32590425b
commit 5ed9ab780a
7 files changed
+253 -328

No files matched your search

@@ -40,6 +40,11 @@ export class InMemoryMonitorsRepository implements IMonitorsRepository {
return this.monitors.filter((m) => monitorIds.includes(m.id)).map((m) => ({ ...m }));
}
async findByIdLean(monitorId: string): Promise<Monitor | null> {
const monitor = this.monitors.find((m) => m.id === monitorId);
return monitor ? { ...monitor } : null;
}
async findByIdsWithChecks(monitorIds: string[], _checksCount?: number): Promise<Monitor[]> {
return this.findByIds(monitorIds);
}
+12 -42
View File
@@ -1,7 +1,5 @@
import { jest } from "@jest/globals";
import { WorkerHelper } from "../../src/worker/worker.helper.ts";
import { MonitorStatusPolicy } from "../../src/worker/worker.monitor-status-policy.ts";
import { CheckPipeline, GeoChecksPipeline } from "../../src/worker/worker.check-pipeline.ts";
import { CheckProducer } from "../../src/worker/worker.check-producer.ts";
import { CheckEvaluator } from "../../src/worker/worker.check-evaluator.ts";
import { NotificationReactor } from "../../src/worker/reactors/reactor.notification.ts";
@@ -71,20 +69,6 @@ const createStubMonitorStatsRepo = () => ({
deleteByMonitorIdsNotIn: jest.fn(),
});
const createStubChecksRepo = () => ({
create: jest.fn(),
createChecks: jest.fn(),
findByMonitorId: jest.fn(),
findByTeamId: jest.fn(),
findLatestByMonitorIds: jest.fn(),
findByDateRangeAndMonitorId: jest.fn(),
findSummaryByTeamId: jest.fn(),
deleteByMonitorId: jest.fn(),
deleteByTeamId: jest.fn(),
deleteByMonitorIdsNotIn: jest.fn(),
deleteOlderThan: jest.fn(),
});
export interface HeartbeatTestHarness {
monitorsRepo: InMemoryMonitorsRepository;
incidentsRepo: InMemoryIncidentsRepository;
@@ -108,13 +92,7 @@ export function createHeartbeatTestHarness(): HeartbeatTestHarness {
const logger = createMockLogger() as any;
const bufferStub = { addToBuffer: jest.fn(), addGeoCheckToBuffer: jest.fn(), scheduleNextFlush: jest.fn() };
const statusService = new StatusService(
logger,
bufferStub as any,
monitorsRepo as any,
createStubMonitorStatsRepo() as any,
createStubChecksRepo() as any
);
const statusService = new StatusService(logger, monitorsRepo as any, createStubMonitorStatsRepo() as any);
const messageBuilder = { extractThresholdBreaches: jest.fn().mockReturnValue([]) };
const incidentService = new IncidentService(logger, incidentsRepo, monitorsRepo as any, { findById: jest.fn() } as any, messageBuilder as any);
@@ -149,7 +127,6 @@ export function createHeartbeatTestHarness(): HeartbeatTestHarness {
};
const maintenanceWindowsRepo = { findByMonitorId: jest.fn().mockResolvedValue([]) };
const geoChecksService = { buildGeoCheck: jest.fn() };
const checkProducer = new CheckProducer(
monitorsRepo as any,
@@ -160,28 +137,21 @@ export function createHeartbeatTestHarness(): HeartbeatTestHarness {
logger
);
const checkEvaluator = new CheckEvaluator(statusService as any, new MonitorStatusPolicy());
const checkPipeline = new CheckPipeline(checkProducer, checkEvaluator);
const geoCheckPipeline = new GeoChecksPipeline(maintenanceWindowsRepo as any, geoChecksService as any, bufferStub as any, logger);
const notificationReactor = new NotificationReactor(notificationsService as any);
const incidentReactor = new IncidentReactor(incidentService as any);
const reactorDispatcher = new ReactorDispatcher(logger, [notificationReactor, incidentReactor]);
const helper = new WorkerHelper(
logger,
checkService as any,
{ getDBSettings: jest.fn() } as any,
monitorsRepo as any,
{ deleteByMonitorIdsNotIn: jest.fn() } as any,
{ findAllTeamIds: jest.fn() } as any,
createStubMonitorStatsRepo() as any,
createStubChecksRepo() as any,
incidentsRepo as any,
{ deleteByMonitorIdsNotIn: jest.fn() } as any,
reactorDispatcher,
checkPipeline,
geoCheckPipeline
);
// Mirror the production check→evaluate flow: read the monitor fresh each cycle (so the
// accumulated statusWindow is visible), produce a check, then evaluate against that monitor.
const heartbeatJob = async (seedMonitor: Monitor) => {
const monitor = await monitorsRepo.findByIdLean(seedMonitor.id);
if (!monitor) return;
const result = await checkProducer.produce(monitor);
if (!result) return; // skipped (e.g. maintenance window)
const evaluation = await checkEvaluator.evaluate(result.status, result.check, monitor);
await reactorDispatcher.dispatch(evaluation);
};
return {
monitorsRepo,
@@ -193,7 +163,7 @@ export function createHeartbeatTestHarness(): HeartbeatTestHarness {
bufferStub,
maintenanceWindowsRepo,
messageBuilder,
heartbeatJob: helper.getHeartbeatJob(),
heartbeatJob,
setNextResponse,
setNextResponseFull,
};
+1 -112
View File
@@ -1,8 +1,5 @@
import { describe, expect, it, jest } from "@jest/globals";
import { CheckPipeline, GeoChecksPipeline } from "../../../src/worker/worker.check-pipeline.ts";
import { CheckProducer } from "../../../src/worker/worker.check-producer.ts";
import { CheckEvaluator } from "../../../src/worker/worker.check-evaluator.ts";
import { MonitorStatusPolicy } from "../../../src/worker/worker.monitor-status-policy.ts";
import { GeoChecksPipeline } from "../../../src/worker/worker.check-pipeline.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
@@ -23,114 +20,6 @@ const activeWindow = () => {
return { active: true, start: new Date(now - 1000).toISOString(), end: new Date(now + 1000).toISOString(), repeat: 0 };
};
// ── CheckPipeline ────────────────────────────────────────────────────────────
const createCheckPipeline = (overrides?: Record<string, any>) => {
const defaults = {
logger: createMockLogger(),
networkService: { requestStatus: jest.fn().mockResolvedValue({ monitorId: "m1", status: true, code: 200, message: "OK" }) },
statusService: {
updateMonitorStatus: jest.fn().mockResolvedValue({ monitor: { id: "m1", status: "up" }, statusChanged: false, prevStatus: "up", code: 200 }),
},
checkService: { toCheck: jest.fn().mockReturnValue({ id: "check-1" }) },
buffer: { addToBuffer: jest.fn() },
monitorsRepository: { updateById: jest.fn().mockResolvedValue({}) },
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([]) },
monitorStatusPolicy: new MonitorStatusPolicy(),
...overrides,
};
const checkProducer = new CheckProducer(
defaults.monitorsRepository as any,
defaults.maintenanceWindowsRepository as any,
defaults.checkService as any,
defaults.networkService as any,
defaults.buffer as any,
defaults.logger as any
);
const checkEvaluator = new CheckEvaluator(defaults.statusService as any, defaults.monitorStatusPolicy as any);
const pipeline = new CheckPipeline(checkProducer, checkEvaluator);
return { pipeline, defaults };
};
describe("CheckPipeline", () => {
it("throws when monitor id is missing", async () => {
const { pipeline } = createCheckPipeline();
await expect(pipeline.run({} as Monitor)).rejects.toThrow("No monitor id");
});
it("skips and sets maintenance status when in maintenance window", async () => {
const { pipeline, defaults } = createCheckPipeline({
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([activeWindow()]) },
});
const result = await pipeline.run(makeMonitor({ status: "up" }));
expect(result).toBeNull();
expect(defaults.networkService.requestStatus).not.toHaveBeenCalled();
expect(defaults.monitorsRepository.updateById).toHaveBeenCalledWith("m1", "team", { status: "maintenance" });
});
it("does not re-write status when already in maintenance", async () => {
const { pipeline, defaults } = createCheckPipeline({
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([activeWindow()]) },
});
await pipeline.run(makeMonitor({ status: "maintenance" }));
expect(defaults.monitorsRepository.updateById).not.toHaveBeenCalled();
});
it("throws when network response is null", async () => {
const { pipeline } = createCheckPipeline({
networkService: { requestStatus: jest.fn().mockResolvedValue(null) },
});
await expect(pipeline.run(makeMonitor())).rejects.toThrow("No network response");
});
it("returns null and warns when toCheck returns null", async () => {
const { pipeline, defaults } = createCheckPipeline({
checkService: { toCheck: jest.fn().mockReturnValue(null) },
});
const result = await pipeline.run(makeMonitor());
expect(result).toBeNull();
expect(defaults.logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining("No check could be built") }));
expect(defaults.statusService.updateMonitorStatus).not.toHaveBeenCalled();
});
it("buffers the check, updates status, and returns an evaluation", async () => {
const status = { monitorId: "m1", status: false, code: 500, message: "Error" };
const statusChange = { monitor: { id: "m1", status: "down" }, statusChanged: true, prevStatus: "up", code: 500 };
const { pipeline, defaults } = createCheckPipeline({
networkService: { requestStatus: jest.fn().mockResolvedValue(status) },
statusService: { updateMonitorStatus: jest.fn().mockResolvedValue(statusChange) },
});
const result = await pipeline.run(makeMonitor());
expect(defaults.buffer.addToBuffer).toHaveBeenCalledWith({ id: "check-1" });
expect(defaults.statusService.updateMonitorStatus).toHaveBeenCalled();
expect(result).toMatchObject({
monitor: statusChange.monitor,
status,
check: { id: "check-1" },
statusChange,
decision: expect.objectContaining({ shouldCreateIncident: true, shouldSendNotification: true, incidentReason: "status_down" }),
});
});
it("proceeds with the check when no maintenance window is active", async () => {
const { pipeline, defaults } = createCheckPipeline({
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([{ ...activeWindow(), active: false }]) },
});
await pipeline.run(makeMonitor());
expect(defaults.networkService.requestStatus).toHaveBeenCalled();
});
});
// ── GeoChecksPipeline ──────────────────────────────────────────────────────────
const createGeoPipeline = (overrides?: Record<string, any>) => {
@@ -0,0 +1,120 @@
import { describe, expect, it, jest } from "@jest/globals";
import { CheckProducer } from "../../../src/worker/worker.check-producer.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
({
id: "m1",
teamId: "team",
type: "http",
interval: 60000,
status: "up",
...overrides,
}) as Monitor;
// A maintenance window straddling "now" — active per isWindowActive.
const activeWindow = () => {
const now = Date.now();
return { active: true, start: new Date(now - 1000).toISOString(), end: new Date(now + 1000).toISOString(), repeat: 0 };
};
const createProducer = (overrides?: Record<string, any>) => {
const defaults = {
logger: createMockLogger(),
monitorsRepository: { updateById: jest.fn().mockResolvedValue({}) },
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([]) },
checkService: { toCheck: jest.fn().mockReturnValue({ id: "check-1" }) },
networkService: { requestStatus: jest.fn().mockResolvedValue({ monitorId: "m1", status: true, code: 200, message: "OK" }) },
buffer: { addToBuffer: jest.fn() },
...overrides,
};
const producer = new CheckProducer(
defaults.monitorsRepository as any,
defaults.maintenanceWindowsRepository as any,
defaults.checkService as any,
defaults.networkService as any,
defaults.buffer as any,
defaults.logger as any
);
return { producer, defaults };
};
describe("CheckProducer", () => {
it("throws when monitor id is missing", async () => {
const { producer } = createProducer();
await expect(producer.produce({} as Monitor)).rejects.toThrow("No monitor id");
});
// ── maintenance gate ──────────────────────────────────────────────────────
it("skips the check and flips status to 'maintenance' when in a maintenance window", async () => {
const { producer, defaults } = createProducer({
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([activeWindow()]) },
});
const result = await producer.produce(makeMonitor({ status: "up" }));
expect(result).toBeNull();
expect(defaults.monitorsRepository.updateById).toHaveBeenCalledWith("m1", "team", { status: "maintenance" });
expect(defaults.networkService.requestStatus).not.toHaveBeenCalled();
expect(defaults.buffer.addToBuffer).not.toHaveBeenCalled();
});
it("does not re-write status when already in maintenance", async () => {
const { producer, defaults } = createProducer({
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([activeWindow()]) },
});
const result = await producer.produce(makeMonitor({ status: "maintenance" }));
expect(result).toBeNull();
expect(defaults.monitorsRepository.updateById).not.toHaveBeenCalled();
});
it("proceeds with the check when the maintenance window is inactive", async () => {
const { producer, defaults } = createProducer({
maintenanceWindowsRepository: { findByMonitorId: jest.fn().mockResolvedValue([{ ...activeWindow(), active: false }]) },
});
await producer.produce(makeMonitor());
expect(defaults.networkService.requestStatus).toHaveBeenCalled();
});
// ── acquire / record ──────────────────────────────────────────────────────
it("throws when the network response is null", async () => {
const { producer } = createProducer({
networkService: { requestStatus: jest.fn().mockResolvedValue(null) },
});
await expect(producer.produce(makeMonitor())).rejects.toThrow("No network response");
});
it("returns null, warns, and does not buffer when toCheck yields nothing", async () => {
const { producer, defaults } = createProducer({
checkService: { toCheck: jest.fn().mockReturnValue(null) },
});
const result = await producer.produce(makeMonitor());
expect(result).toBeNull();
expect(defaults.logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining("No check could be built") }));
expect(defaults.buffer.addToBuffer).not.toHaveBeenCalled();
});
it("buffers the built check and returns the status and check on success", async () => {
const status = { monitorId: "m1", status: false, code: 500, message: "Error" };
const check = { id: "check-1" };
const { producer, defaults } = createProducer({
networkService: { requestStatus: jest.fn().mockResolvedValue(status) },
checkService: { toCheck: jest.fn().mockReturnValue(check) },
});
const result = await producer.produce(makeMonitor());
expect(defaults.buffer.addToBuffer).toHaveBeenCalledWith(check);
expect(result).toEqual({ status, check });
});
});
@@ -80,6 +80,7 @@ const createWorker = (overrides?: { queueMode?: QueueMode; queuePrimaryProcesses
};
const monitorsRepository = {
findByIds: jest.fn<any>().mockResolvedValue([makeMonitor()]),
findByIdLean: jest.fn<any>().mockResolvedValue(makeMonitor()),
findAll: jest.fn<any>().mockResolvedValue([]),
updateById: jest.fn<any>().mockResolvedValue({}),
};
@@ -94,7 +95,7 @@ const createWorker = (overrides?: { queueMode?: QueueMode; queuePrimaryProcesses
produce: jest.fn<any>().mockResolvedValue({ status: { status: "up" }, check: { id: "c1" } }),
};
const checkEvaluator = {
evaluate: jest.fn<any>().mockResolvedValue({ monitor: makeMonitor(), decision: {} }),
evaluate: jest.fn<any>().mockResolvedValue({ monitor: makeMonitor(), statusChange: { monitor: makeMonitor() }, decision: {} }),
};
const geoCheckPipeline = { run: jest.fn<any>().mockResolvedValue(null) };
const dispatcher = { dispatch: jest.fn<any>().mockResolvedValue(undefined) };
@@ -268,7 +269,7 @@ describe("DBQueueWorker", () => {
const job = makeJob({ type: "check", refId: "m1", intervalMs: 60000 });
const { mocks } = await start({ mocks: { jobsRepository: { ...createWorker().mocks.jobsRepository, claimDueBatch: claimOnce(job) } } });
expect(mocks.monitorsRepository.findByIds).toHaveBeenCalledWith(["m1"]);
expect(mocks.monitorsRepository.findByIdLean).toHaveBeenCalledWith("m1");
expect(mocks.checkProducer.produce).toHaveBeenCalled();
// The check stage only produces; the buffer arms evaluate once the check is durably stored
expect(mocks.jobsRepository.upsertEvaluate).not.toHaveBeenCalled();
@@ -307,6 +308,35 @@ describe("DBQueueWorker", () => {
expect(mocks.monitorsRepository.updateById).toHaveBeenCalledWith("m1", "team", { lastEvaluatedAt: 12345 });
});
it("evaluate job reads the monitor once and threads the post-write monitor across the backlog", async () => {
const job = makeJob({ id: "evaluate:m1", type: "evaluate", intervalMs: null });
const initialMonitor = makeMonitor();
const postWriteMonitor = makeMonitor({ status: "down" }); // what updateStatusWindowAndChecks would return after check c1
const monitorsRepository = {
findByIdLean: jest.fn<any>().mockResolvedValue(initialMonitor),
updateById: jest.fn<any>().mockResolvedValue({}),
};
const checksRepository = { findUnevaluatedByMonitorId: jest.fn<any>().mockResolvedValue([{ id: "c1" }, { id: "c2" }]) };
const checkEvaluator = {
evaluate: jest.fn<any>().mockResolvedValue({ monitor: postWriteMonitor, statusChange: { monitor: postWriteMonitor }, decision: {} }),
};
const { mocks } = await start({
mocks: {
jobsRepository: { ...createWorker().mocks.jobsRepository, claimDueBatch: claimOnce(job) },
monitorsRepository,
checksRepository,
checkEvaluator,
},
});
// The monitor is read once for the whole backlog, not once per check
expect(mocks.monitorsRepository.findByIdLean).toHaveBeenCalledTimes(1);
expect(mocks.checkEvaluator.evaluate).toHaveBeenCalledTimes(2);
// First check evaluates against the freshly-read monitor; the second against the post-write monitor from the first
expect(mocks.checkEvaluator.evaluate.mock.calls[0][2]).toBe(initialMonitor);
expect(mocks.checkEvaluator.evaluate.mock.calls[1][2]).toBe(postWriteMonitor);
});
it("cleanup-orphaned job runs the helper's cleanup function", async () => {
const job = makeJob({ id: "cleanup-orphaned", type: "cleanup-orphaned", refId: null, intervalMs: 86400000 });
const cleanupFn = jest.fn<any>().mockResolvedValue(undefined);
+80 -82
View File
@@ -7,21 +7,15 @@ import type { Check } from "../../../src/domain/checks/check.type.ts";
import type { MonitorStatusResponse, HardwareStatusPayload } from "../../../src/types/network.ts";
import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts";
import type { IMonitorStatsRepository } from "../../../src/domain/monitor-stats/monitor-stats.repository.interface.ts";
import type { IChecksRepository } from "../../../src/domain/checks/check.repository.interface.ts";
import type { IBufferService } from "../../../src/service/bufferService.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
const createBuffer = (): jest.Mocked<Pick<IBufferService, "addToBuffer">> => ({
addToBuffer: jest.fn(),
});
const createMonitorsRepo = () => {
const findById = jest.fn();
const updateStatusWindowAndChecks = jest
.fn()
.mockImplementation(
(
async (
_id: unknown,
_tid: unknown,
status: boolean,
@@ -30,27 +24,23 @@ const createMonitorsRepo = () => {
maxRecentChecks: number,
statusPatch?: Partial<Monitor>
) => {
// Grab the monitor that findById was configured to resolve with
const monitor = findById.mock.results.at(-1)?.value;
if (monitor && typeof monitor.then === "function") {
return monitor.then((m: any) => {
m.statusWindow = m.statusWindow || [];
m.statusWindow.push(status);
while (m.statusWindow.length > windowSize) {
m.statusWindow.shift();
}
m.recentChecks = m.recentChecks || [];
m.recentChecks.push(checkSnapshot);
while (m.recentChecks.length > maxRecentChecks) {
m.recentChecks.shift();
}
if (statusPatch) {
Object.assign(m, statusPatch);
}
return { ...m };
});
// updateMonitorStatus no longer reads the monitor itself — tests seed it via
// findById.mockResolvedValue, so the mock resolves it here as the post-write monitor.
const m: any = await findById();
m.statusWindow = m.statusWindow || [];
m.statusWindow.push(status);
while (m.statusWindow.length > windowSize) {
m.statusWindow.shift();
}
return Promise.reject(new Error("findById not mocked"));
m.recentChecks = m.recentChecks || [];
m.recentChecks.push(checkSnapshot);
while (m.recentChecks.length > maxRecentChecks) {
m.recentChecks.shift();
}
if (statusPatch) {
Object.assign(m, statusPatch);
}
return { ...m };
}
);
return {
@@ -67,23 +57,17 @@ const createMonitorStatsRepo = () =>
updateByMonitorId: jest.fn(),
}) as unknown as jest.Mocked<IMonitorStatsRepository>;
const createChecksRepo = () => ({}) as unknown as jest.Mocked<IChecksRepository>;
const createService = (overrides?: {
logger?: ReturnType<typeof createMockLogger>;
buffer?: ReturnType<typeof createBuffer>;
monitorsRepository?: ReturnType<typeof createMonitorsRepo>;
monitorStatsRepository?: ReturnType<typeof createMonitorStatsRepo>;
checksRepository?: ReturnType<typeof createChecksRepo>;
}) => {
const logger = overrides?.logger ?? createMockLogger();
const buffer = overrides?.buffer ?? createBuffer();
const monitorsRepository = overrides?.monitorsRepository ?? createMonitorsRepo();
const monitorStatsRepository = overrides?.monitorStatsRepository ?? createMonitorStatsRepo();
const checksRepository = overrides?.checksRepository ?? createChecksRepo();
const service = new StatusService(logger as any, buffer as any, monitorsRepository, monitorStatsRepository, checksRepository);
return { service, logger, buffer, monitorsRepository, monitorStatsRepository, checksRepository };
const service = new StatusService(logger as any, monitorsRepository, monitorStatsRepository);
return { service, logger, monitorsRepository, monitorStatsRepository };
};
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
@@ -264,19 +248,31 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck());
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck(), monitor);
expect(result.statusChanged).toBe(false);
expect(result.prevStatus).toBe("up");
});
it("uses the prefetched monitor and skips findById when one is passed", async () => {
const monitor = makeMonitor({ statusWindow: [], statusWindowSize: 5, status: "up" });
const { service, monitorsRepository } = createService();
(monitorsRepository.updateStatusWindowAndChecks as jest.Mock).mockResolvedValue(monitor);
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck(), monitor);
expect(monitorsRepository.findById).not.toHaveBeenCalled();
expect(result.prevStatus).toBe("up");
expect(result.monitor).toBe(monitor);
});
it("pushes to statusWindow and trims to statusWindowSize", async () => {
const monitor = makeMonitor({ statusWindow: [true, true, true, true, true], statusWindowSize: 5 });
const { service, monitorsRepository } = createService();
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
// Atomic push should have been called with the correct status and window size
expect(monitorsRepository.updateStatusWindowAndChecks).toHaveBeenCalledWith(
@@ -297,7 +293,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
await service.updateMonitorStatus(makeStatusResponse(), makeCheck({ id: "new-check" }));
await service.updateMonitorStatus(makeStatusResponse(), makeCheck({ id: "new-check" }), monitor);
expect(monitor.recentChecks).toHaveLength(MAX_RECENT_CHECKS);
expect(monitor.recentChecks[MAX_RECENT_CHECKS - 1]).toEqual(expect.objectContaining({ id: "new-check" }));
@@ -315,7 +311,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
expect(result.statusChanged).toBe(true);
expect(result.monitor.status).toBe("down");
@@ -333,7 +329,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }), monitor);
expect(result.statusChanged).toBe(true);
expect(result.monitor.status).toBe("up");
@@ -350,7 +346,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck());
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck(), monitor);
expect(result.statusChanged).toBe(false);
expect(result.monitor.status).toBe("up");
@@ -370,7 +366,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
expect(result.statusChanged).toBe(false);
expect(result.monitor.status).toBe("up");
@@ -387,7 +383,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
expect(result.statusChanged).toBe(false);
expect(result.monitor.status).toBe("down");
@@ -399,7 +395,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
await service.updateMonitorStatus(makeStatusResponse(), makeCheck());
await service.updateMonitorStatus(makeStatusResponse(), makeCheck(), monitor);
expect(monitor.statusWindow).toEqual([true]);
});
@@ -410,23 +406,25 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
await service.updateMonitorStatus(makeStatusResponse(), makeCheck());
await service.updateMonitorStatus(makeStatusResponse(), makeCheck(), monitor);
expect(monitor.recentChecks).toHaveLength(1);
});
it("throws AppError when repository throws", async () => {
const monitor = makeMonitor();
const { service, monitorsRepository } = createService();
(monitorsRepository.findById as jest.Mock).mockRejectedValue(new Error("db error"));
(monitorsRepository.updateStatusWindowAndChecks as jest.Mock).mockRejectedValue(new Error("db error"));
await expect(service.updateMonitorStatus(makeStatusResponse(), makeCheck())).rejects.toThrow("Failed to update monitor");
await expect(service.updateMonitorStatus(makeStatusResponse(), makeCheck(), monitor)).rejects.toThrow("Failed to update monitor");
});
it("throws AppError with 'Unknown error' for non-Error exceptions", async () => {
const monitor = makeMonitor();
const { service, monitorsRepository } = createService();
(monitorsRepository.findById as jest.Mock).mockRejectedValue("string error");
(monitorsRepository.updateStatusWindowAndChecks as jest.Mock).mockRejectedValue("string error");
await expect(service.updateMonitorStatus(makeStatusResponse(), makeCheck())).rejects.toThrow("Unknown error");
await expect(service.updateMonitorStatus(makeStatusResponse(), makeCheck(), monitor)).rejects.toThrow("Unknown error");
});
it("keeps status 'down' on a single successful check that leaves failure rate at threshold (regression: #3438 mirror)", async () => {
@@ -444,7 +442,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }), monitor);
expect(result.statusChanged).toBe(false);
expect(result.monitor.status).toBe("down");
@@ -462,7 +460,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
expect(result.statusChanged).toBe(true);
expect(result.monitor.status).toBe("down");
@@ -484,7 +482,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }), monitor);
expect(result.monitor.status).toBe("up");
});
@@ -502,7 +500,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
expect(result.monitor.status).toBe("down");
});
@@ -518,7 +516,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
expect(result.statusChanged).toBe(false);
expect(result.monitor.status).toBe("up");
@@ -535,7 +533,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
expect(result.statusChanged).toBe(true);
expect(result.monitor.status).toBe("down");
@@ -550,7 +548,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }), monitor);
expect(result.statusChanged).toBe(true);
expect(result.monitor.status).toBe("down");
@@ -567,7 +565,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }), monitor);
expect(result.statusChanged).toBe(false);
expect(result.monitor.status).toBe("down");
@@ -580,7 +578,7 @@ describe("StatusService", () => {
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
(monitorStatsRepository.updateByMonitorId as jest.Mock).mockRejectedValue(new Error("stats db down"));
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck());
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck(), monitor);
expect(result.monitor.status).toBe("up");
expect(logger.warn).toHaveBeenCalledWith(
@@ -598,7 +596,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse({ code: 201 }), makeCheck());
const result = await service.updateMonitorStatus(makeStatusResponse({ code: 201 }), makeCheck(), monitor);
expect(result.code).toBe(201);
expect(result.timestamp).toBeGreaterThan(0);
@@ -639,7 +637,7 @@ describe("StatusService", () => {
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const response = makeHardwareResponse({ data: { cpu: { usage_percent: 0.9 }, memory: { usage_percent: 0.5 }, disk: [], host: {} } } as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.cpu).toBe(true);
expect(result.thresholdBreaches?.memory).toBe(false);
@@ -656,7 +654,7 @@ describe("StatusService", () => {
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const response = makeHardwareResponse({ data: { cpu: { usage_percent: 0.5 }, memory: { usage_percent: 0.9 }, disk: [], host: {} } } as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.memory).toBe(true);
const patch = (monitorsRepository.updateStatusWindowAndChecks as jest.Mock).mock.calls.at(-1)?.[6];
@@ -673,7 +671,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1 }, memory: { usage_percent: 0.1 }, disk: [{ usage_percent: 0.95 }], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.disk).toBe(true);
const patch = (monitorsRepository.updateStatusWindowAndChecks as jest.Mock).mock.calls.at(-1)?.[6];
@@ -690,7 +688,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1, temperature: [90] }, memory: { usage_percent: 0.1 }, disk: [], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.temp).toBe(true);
const patch = (monitorsRepository.updateStatusWindowAndChecks as jest.Mock).mock.calls.at(-1)?.[6];
@@ -712,7 +710,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1, temperature: [30] }, memory: { usage_percent: 0.1 }, disk: [{ usage_percent: 0.1 }], host: {} },
} as any);
await service.updateMonitorStatus(response, makeCheck());
await service.updateMonitorStatus(response, makeCheck(), monitor);
const patch = (monitorsRepository.updateStatusWindowAndChecks as jest.Mock).mock.calls.at(-1)?.[6];
expect(patch.cpuAlertCounter).toBe(5);
@@ -731,7 +729,7 @@ describe("StatusService", () => {
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const response = makeHardwareResponse({ data: { cpu: { usage_percent: 0.9 }, memory: { usage_percent: 0.1 }, disk: [], host: {} } } as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.monitor.status).toBe("breached");
expect(result.statusChanged).toBe(false);
@@ -746,7 +744,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1, temperature: [30] }, memory: { usage_percent: 0.1 }, disk: [{ usage_percent: 0.1 }], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.statusChanged).toBe(true);
expect(result.monitor.status).toBe("up");
@@ -769,7 +767,7 @@ describe("StatusService", () => {
status: false,
data: { cpu: { usage_percent: 0.9 }, memory: { usage_percent: 0.1 }, disk: [], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck({ status: false }));
const result = await service.updateMonitorStatus(response, makeCheck({ status: false }), monitor);
expect(result.monitor.status).toBe("down");
});
@@ -781,7 +779,7 @@ describe("StatusService", () => {
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const response = makeHardwareResponse({ data: { cpu: {}, memory: { usage_percent: 0.1 }, disk: [], host: {} } } as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.cpu).toBe(false);
});
@@ -793,7 +791,7 @@ describe("StatusService", () => {
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const response = makeHardwareResponse({ data: { cpu: { usage_percent: 0.1 }, memory: {}, disk: [], host: {} } } as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.memory).toBe(false);
});
@@ -807,7 +805,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1, temperature: [] }, memory: { usage_percent: 0.1 }, disk: [], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.temp).toBe(false);
});
@@ -821,7 +819,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1 }, memory: { usage_percent: 0.1 }, disk: [], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.temp).toBe(false);
});
@@ -833,7 +831,7 @@ describe("StatusService", () => {
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const response = makeHardwareResponse(undefined as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches).toBeUndefined();
});
@@ -845,7 +843,7 @@ describe("StatusService", () => {
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const response = makeHardwareResponse({} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches).toBeUndefined();
});
@@ -860,7 +858,7 @@ describe("StatusService", () => {
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const result = await service.updateMonitorStatus(makeStatusResponse(), makeCheck());
const result = await service.updateMonitorStatus(makeStatusResponse(), makeCheck(), monitor);
expect(result.thresholdBreaches).toBeUndefined();
});
@@ -874,7 +872,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1 }, memory: { usage_percent: 0.1 }, disk: [null as any], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.disk).toBe(false);
});
@@ -888,7 +886,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1 }, memory: { usage_percent: 0.1 }, disk: [{ device: "/dev/sda" }], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.disk).toBe(false);
});
@@ -902,7 +900,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: undefined, memory: { usage_percent: 0.1 }, disk: [], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.cpu).toBe(false);
expect(result.thresholdBreaches?.temp).toBe(false);
@@ -917,7 +915,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1 }, memory: undefined, disk: [], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.memory).toBe(false);
});
@@ -931,7 +929,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.1 }, memory: { usage_percent: 0.1 }, disk: undefined, host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(result.thresholdBreaches?.disk).toBe(false);
});
@@ -945,7 +943,7 @@ describe("StatusService", () => {
(monitorsRepository.updateById as jest.Mock).mockImplementation((_id: unknown, _tid: unknown, m: unknown) => Promise.resolve(m));
const response = makeHardwareResponse({ data: { cpu: { usage_percent: 0.9 }, memory: { usage_percent: 0.1 }, disk: [], host: {} } } as any);
const result = await service.updateMonitorStatus(response, makeCheck());
const result = await service.updateMonitorStatus(response, makeCheck(), monitor);
const patch = (monitorsRepository.updateStatusWindowAndChecks as jest.Mock).mock.calls.at(-1)?.[6];
expect(patch.cpuAlertCounter).toBe(2);
@@ -971,7 +969,7 @@ describe("StatusService", () => {
status: false,
data: { cpu: { usage_percent: 0.1 }, memory: { usage_percent: 0.1 }, disk: [], host: {} },
} as any);
const result = await service.updateMonitorStatus(response, makeCheck({ status: false }));
const result = await service.updateMonitorStatus(response, makeCheck({ status: false }), monitor);
expect(result.statusChanged).toBe(true);
expect(result.monitor.status).toBe("down");
@@ -991,7 +989,7 @@ describe("StatusService", () => {
const response = makeHardwareResponse({
data: { cpu: { usage_percent: 0.9, temperature: [90] }, memory: { usage_percent: 0.9 }, disk: [{ usage_percent: 0.95 }], host: {} },
} as any);
await service.updateMonitorStatus(response, makeCheck());
await service.updateMonitorStatus(response, makeCheck(), monitor);
expect(monitor.cpuAlertCounter).toBe(0);
expect(monitor.memoryAlertCounter).toBe(0);
+3 -90
View File
@@ -1,14 +1,11 @@
import { describe, expect, it, jest } from "@jest/globals";
import { WorkerHelper } from "../../../src/worker/worker.helper.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
//
// WorkerHelper is now thin glue: it wires the scheduler's job functions to the
// CheckPipeline / GeoChecksPipeline (acquire→record→decide) and the
// ReactorDispatcher (react), and owns the cleanup jobs. The pipeline and
// dispatcher internals are tested in checkPipeline.test.ts / reactorDispatcher.test.ts;
// WorkerHelper is now thin glue: it wires the geo job to the GeoChecksPipeline and
// owns the cleanup jobs. The pipeline internals are tested in checkPipeline.test.ts;
// here we test only the glue + cleanup jobs.
const createHelper = (overrides?: Record<string, unknown>) => {
@@ -42,10 +39,6 @@ const createHelper = (overrides?: Record<string, unknown>) => {
deleteOlderThan: jest.fn().mockResolvedValue(0),
};
const checkPipeline = { run: jest.fn().mockResolvedValue(null) };
const geoCheckPipeline = { run: jest.fn().mockResolvedValue(null) };
const reactorDispatcher = { dispatch: jest.fn().mockResolvedValue(undefined) };
const defaults = {
logger: createMockLogger(),
checkService,
@@ -57,9 +50,6 @@ const createHelper = (overrides?: Record<string, unknown>) => {
checksRepository,
incidentsRepository,
geoChecksRepository,
reactorDispatcher,
checkPipeline,
geoCheckPipeline,
...overrides,
};
@@ -73,26 +63,11 @@ const createHelper = (overrides?: Record<string, unknown>) => {
defaults.monitorStatsRepository as any,
defaults.checksRepository as any,
defaults.incidentsRepository as any,
defaults.geoChecksRepository as any,
defaults.reactorDispatcher as any,
defaults.checkPipeline as any,
defaults.geoCheckPipeline as any
defaults.geoChecksRepository as any
);
return { helper, defaults };
};
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
({
id: "m1",
teamId: "team",
type: "http",
interval: 60000,
status: "up",
geoCheckEnabled: false,
geoCheckLocations: [],
...overrides,
}) as Monitor;
// ── Tests ────────────────────────────────────────────────────────────────────
describe("WorkerHelper", () => {
@@ -103,68 +78,6 @@ describe("WorkerHelper", () => {
});
});
// ── getHeartbeatJob (glue) ────────────────────────────────────────────────
describe("getHeartbeatJob", () => {
it("runs the pipeline and dispatches the evaluation to reactors", async () => {
const evaluation = { monitor: { id: "m1" }, decision: {} };
const { helper, defaults } = createHelper({ checkPipeline: { run: jest.fn().mockResolvedValue(evaluation) } });
await helper.getHeartbeatJob()(makeMonitor());
expect(defaults.checkPipeline.run).toHaveBeenCalled();
expect(defaults.reactorDispatcher.dispatch).toHaveBeenCalledWith(evaluation);
});
it("does not dispatch when the pipeline returns null (skipped)", async () => {
const { helper, defaults } = createHelper({ checkPipeline: { run: jest.fn().mockResolvedValue(null) } });
await helper.getHeartbeatJob()(makeMonitor());
expect(defaults.reactorDispatcher.dispatch).not.toHaveBeenCalled();
});
it("logs and rethrows when the pipeline throws", async () => {
const { helper, defaults } = createHelper({ checkPipeline: { run: jest.fn().mockRejectedValue(new Error("boom")) } });
await expect(helper.getHeartbeatJob()(makeMonitor())).rejects.toThrow("boom");
expect(defaults.logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "boom" }));
});
it("rethrows non-Error failures", async () => {
const { helper } = createHelper({ checkPipeline: { run: jest.fn().mockRejectedValue("unexpected") } });
await expect(helper.getHeartbeatJob()(makeMonitor())).rejects.toBe("unexpected");
});
});
// ── getHeartbeatGeoJob (glue) ─────────────────────────────────────────────
describe("getHeartbeatGeoJob", () => {
it("runs the geo pipeline", async () => {
const { helper, defaults } = createHelper();
await helper.getHeartbeatGeoJob()(makeMonitor());
expect(defaults.geoCheckPipeline.run).toHaveBeenCalled();
});
it("catches and logs a pipeline error without rethrowing", async () => {
const { helper, defaults } = createHelper({ geoCheckPipeline: { run: jest.fn().mockRejectedValue(new Error("api timeout")) } });
await expect(helper.getHeartbeatGeoJob()(makeMonitor())).resolves.toBeUndefined();
expect(defaults.logger.error).toHaveBeenCalledWith(expect.objectContaining({ message: "api timeout" }));
});
it("logs 'Unknown error' for non-Error failures", async () => {
const { helper, defaults } = createHelper({ geoCheckPipeline: { run: jest.fn().mockRejectedValue("string error") } });
await helper.getHeartbeatGeoJob()(makeMonitor());
expect(defaults.logger.error).toHaveBeenCalledWith(expect.objectContaining({ message: "Unknown error", stack: undefined }));
});
});
// ── getCleanupOrphanedJob ────────────────────────────────────────────────
describe("getCleanupOrphanedJob", () => {