From 5fa74bd88312e1f0a2f7b2234bede4901cd7a7f9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 4 Aug 2026 15:16:03 -0700 Subject: [PATCH] replace findAll with findAllForScheduling --- .../monitors/monitor.repository.interface.ts | 4 ++-- .../monitors/monitor.repository.mongo.ts | 19 +++++++++++++------ server/src/domain/monitors/monitor.type.ts | 2 ++ server/src/worker/worker.job-scheduler.ts | 8 ++++---- .../helpers/InMemoryMonitorsRepository.ts | 13 ++++++++++--- .../test/unit/services/dbQueueWorker.test.ts | 14 +++++++++++--- .../test/unit/services/jobScheduler.test.ts | 10 +++++----- 7 files changed, 47 insertions(+), 23 deletions(-) diff --git a/server/src/domain/monitors/monitor.repository.interface.ts b/server/src/domain/monitors/monitor.repository.interface.ts index e743e423f..e6e68863b 100644 --- a/server/src/domain/monitors/monitor.repository.interface.ts +++ b/server/src/domain/monitors/monitor.repository.interface.ts @@ -1,4 +1,4 @@ -import type { MonitorType, Monitor, MonitorStatus, MonitorsSummary, CheckSnapshot } from "@/domain/monitors/monitor.type.js"; +import type { MonitorType, Monitor, MonitorStatus, MonitorsSummary, CheckSnapshot, MonitorScheduleFields } from "@/domain/monitors/monitor.type.js"; export interface TeamQueryConfig { limit?: number; @@ -26,7 +26,7 @@ export interface IMonitorsRepository { findByIdLean(monitorId: string): Promise; // collection fetch - findAll(): Promise; + findAllForScheduling(): Promise; findByTeamId(teamId: string, config: TeamQueryConfig): Promise; findByTeamIdWithStats(teamId: string, config: TeamQueryConfig): Promise; findByIds(monitorIds: string[]): Promise; diff --git a/server/src/domain/monitors/monitor.repository.mongo.ts b/server/src/domain/monitors/monitor.repository.mongo.ts index 68065f332..cdfbf4ca9 100644 --- a/server/src/domain/monitors/monitor.repository.mongo.ts +++ b/server/src/domain/monitors/monitor.repository.mongo.ts @@ -1,7 +1,7 @@ import { MonitorModel } from "@/domain/monitors/monitor.model.js"; import type { MonitorDocument, CheckSnapshotDocument } from "@/domain/monitors/monitor.model.js"; import type { CheckSnapshot } from "@/domain/checks/check.type.js"; -import type { Monitor, MonitorStatus, MonitorsSummary } from "@/domain/monitors/monitor.type.js"; +import type { Monitor, MonitorScheduleFields, MonitorStatus, MonitorsSummary } from "@/domain/monitors/monitor.type.js"; import mongoose, { type FilterQuery, type PipelineStage } from "mongoose"; import { MongoBulkWriteError } from "mongodb"; import { AppError } from "@/utils/AppError.js"; @@ -41,13 +41,20 @@ class MongoMonitorsRepository implements IMonitorsRepository { }; findByIdLean = async (monitorId: string): Promise => { - const monitor = await MonitorModel.findOne({ _id: monitorId }); + const monitor = await MonitorModel.findOne({ _id: monitorId }).select({ recentChecks: 0 }); return monitor ? this.toEntity(monitor) : null; }; - findAll = async (): Promise => { - const monitors = await MonitorModel.find(); - return this.mapDocuments(monitors); + findAllForScheduling = async (): Promise => { + const docs = await MonitorModel.find({}, { type: 1, isActive: 1, interval: 1, geoCheckEnabled: 1, geoCheckInterval: 1 }).lean(); + return docs.map((doc) => ({ + id: doc._id.toString(), + type: doc.type, + isActive: doc.isActive, + interval: doc.interval, + geoCheckEnabled: doc.geoCheckEnabled ?? false, + geoCheckInterval: doc.geoCheckInterval ?? 300000, + })); }; private queryBuilder = (config: TeamQueryConfig, teamId: string): FilterQuery => { @@ -182,7 +189,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { }, ...(statusPatch && { $set: statusPatch }), }, - { returnDocument: "after" } + { returnDocument: "after", projection: { recentChecks: 0 } } ); if (!updatedMonitor) { diff --git a/server/src/domain/monitors/monitor.type.ts b/server/src/domain/monitors/monitor.type.ts index 865bafd76..0fb1ed397 100644 --- a/server/src/domain/monitors/monitor.type.ts +++ b/server/src/domain/monitors/monitor.type.ts @@ -178,3 +178,5 @@ export interface Game { } export type GamesMap = Record; + +export type MonitorScheduleFields = Pick; diff --git a/server/src/worker/worker.job-scheduler.ts b/server/src/worker/worker.job-scheduler.ts index 8d103e87f..aae8a489b 100644 --- a/server/src/worker/worker.job-scheduler.ts +++ b/server/src/worker/worker.job-scheduler.ts @@ -1,7 +1,7 @@ import { IJobScheduler } from "@/worker/worker.interface.js"; import { IJobsRepository } from "@/domain/jobs/job.repository.interface.js"; import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; -import { Monitor, supportsGeoCheck } from "@/domain/monitors/monitor.type.js"; +import { Monitor, MonitorScheduleFields, supportsGeoCheck } from "@/domain/monitors/monitor.type.js"; import { type Job, type JobSeed, type JobType, jobId } from "@/domain/jobs/job.type.js"; import { IQueueWorkersRepository } from "@/domain/queue-workers/queue-worker.repository.interface.js"; import { WorkerJobsPagination, WorkerJobSummary, WorkerMetrics } from "@/worker/worker.interface.js"; @@ -43,7 +43,7 @@ export class JobScheduler implements IJobScheduler { "cleanup-retention": POLL_MS, }; // Helpers, builds job seeds. immediate=true runs on the next tick; otherwise jitter spreads the herd. - protected toCheckJob = (monitor: Monitor, now: number, immediate = false): JobSeed => ({ + protected toCheckJob = (monitor: MonitorScheduleFields, now: number, immediate = false): JobSeed => ({ id: jobId("check", monitor.id), type: "check", refId: monitor.id, @@ -52,7 +52,7 @@ export class JobScheduler implements IJobScheduler { intervalMs: monitor.interval, }); - protected toGeoCheckJob = (monitor: Monitor, now: number, immediate = false): JobSeed => ({ + protected toGeoCheckJob = (monitor: MonitorScheduleFields, now: number, immediate = false): JobSeed => ({ ...this.toCheckJob(monitor, now, immediate), id: jobId("geo-check", monitor.id), type: "geo-check", @@ -187,7 +187,7 @@ export class JobScheduler implements IJobScheduler { // Seed queue protected reconcile = async () => { const now = Date.now(); - const monitors = await this.monitorsRepository.findAll(); + const monitors = await this.monitorsRepository.findAllForScheduling(); for (const monitor of monitors) { await this.jobsRepository.upsertJob(this.toCheckJob(monitor, now)); if (supportsGeoCheck(monitor.type) && monitor.geoCheckEnabled) { diff --git a/server/test/helpers/InMemoryMonitorsRepository.ts b/server/test/helpers/InMemoryMonitorsRepository.ts index fa811ad62..2ec36e8e2 100644 --- a/server/test/helpers/InMemoryMonitorsRepository.ts +++ b/server/test/helpers/InMemoryMonitorsRepository.ts @@ -1,6 +1,6 @@ import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "../../src/domain/monitors/monitor.repository.interface.ts"; import type { CheckSnapshot } from "../../src/domain/checks/check.type.ts"; -import type { Monitor, MonitorsSummary } from "../../src/domain/monitors/monitor.type.ts"; +import type { Monitor, MonitorScheduleFields, MonitorsSummary } from "../../src/domain/monitors/monitor.type.ts"; export class InMemoryMonitorsRepository implements IMonitorsRepository { private monitors: Monitor[] = []; @@ -24,8 +24,15 @@ export class InMemoryMonitorsRepository implements IMonitorsRepository { return { ...monitor }; } - async findAll(): Promise { - return this.monitors.map((m) => ({ ...m })); + async findAllForScheduling(): Promise { + return this.monitors.map((m) => ({ + id: m.id, + type: m.type, + isActive: m.isActive, + interval: m.interval, + geoCheckEnabled: m.geoCheckEnabled ?? false, + geoCheckInterval: m.geoCheckInterval ?? 300000, + })); } async findByTeamId(_teamId: string, _config: TeamQueryConfig): Promise { diff --git a/server/test/unit/services/dbQueueWorker.test.ts b/server/test/unit/services/dbQueueWorker.test.ts index d903ae887..8b13287d4 100644 --- a/server/test/unit/services/dbQueueWorker.test.ts +++ b/server/test/unit/services/dbQueueWorker.test.ts @@ -84,7 +84,7 @@ const createWorker = (overrides?: { queueMode?: QueueMode; queuePrimaryProcesses const monitorsRepository = { findByIds: jest.fn().mockResolvedValue([makeMonitor()]), findByIdLean: jest.fn().mockResolvedValue(makeMonitor()), - findAll: jest.fn().mockResolvedValue([]), + findAllForScheduling: jest.fn().mockResolvedValue([]), updateById: jest.fn().mockResolvedValue({}), }; const checksRepository = { @@ -183,7 +183,11 @@ describe("DBQueueWorker", () => { queueMode: "primary", queuePrimaryProcesses: false, // isolate reconcile from the polling loops mocks: { - monitorsRepository: { findByIds: jest.fn(), findAll: jest.fn().mockResolvedValue([makeMonitor()]), updateById: jest.fn() }, + monitorsRepository: { + findByIds: jest.fn(), + findAllForScheduling: jest.fn().mockResolvedValue([makeMonitor()]), + updateById: jest.fn(), + }, }, }); @@ -200,7 +204,11 @@ describe("DBQueueWorker", () => { queueMode: "primary", queuePrimaryProcesses: false, mocks: { - monitorsRepository: { findByIds: jest.fn(), findAll: jest.fn().mockResolvedValue([geoMonitor]), updateById: jest.fn() }, + monitorsRepository: { + findByIds: jest.fn(), + findAllForScheduling: jest.fn().mockResolvedValue([geoMonitor]), + updateById: jest.fn(), + }, }, }); diff --git a/server/test/unit/services/jobScheduler.test.ts b/server/test/unit/services/jobScheduler.test.ts index 098cef245..90c8c2850 100644 --- a/server/test/unit/services/jobScheduler.test.ts +++ b/server/test/unit/services/jobScheduler.test.ts @@ -38,7 +38,7 @@ const createScheduler = (overrides?: { queueMode?: QueueMode; mocks?: Record().mockResolvedValue(undefined), }; const monitorsRepository = { - findAll: jest.fn().mockResolvedValue([]), + findAllForScheduling: jest.fn().mockResolvedValue([]), }; const logger = createMockLogger(); const mocks = { jobsRepository, queueWorkersRepository, monitorsRepository, logger, ...overrides?.mocks }; @@ -73,7 +73,7 @@ describe("JobScheduler", () => { it("primary mode reconciles: seeds a check row per monitor plus the two cleanup rows", async () => { const { scheduler, mocks } = createScheduler({ queueMode: "primary", - mocks: { monitorsRepository: { findAll: jest.fn().mockResolvedValue([makeMonitor()]) } }, + mocks: { monitorsRepository: { findAllForScheduling: jest.fn().mockResolvedValue([makeMonitor()]) } }, }); await scheduler.init(); @@ -87,7 +87,7 @@ describe("JobScheduler", () => { it("primary mode also seeds a geo-check row for geo-enabled monitors", async () => { const { scheduler, mocks } = createScheduler({ queueMode: "primary", - mocks: { monitorsRepository: { findAll: jest.fn().mockResolvedValue([makeMonitor({ geoCheckEnabled: true })]) } }, + mocks: { monitorsRepository: { findAllForScheduling: jest.fn().mockResolvedValue([makeMonitor({ geoCheckEnabled: true })]) } }, }); await scheduler.init(); @@ -98,12 +98,12 @@ describe("JobScheduler", () => { it("worker mode heartbeats but does not reconcile (no monitor reads, no seeds)", async () => { const { scheduler, mocks } = createScheduler({ queueMode: "worker", - mocks: { monitorsRepository: { findAll: jest.fn().mockResolvedValue([makeMonitor()]) } }, + mocks: { monitorsRepository: { findAllForScheduling: jest.fn().mockResolvedValue([makeMonitor()]) } }, }); await scheduler.init(); expect(mocks.queueWorkersRepository.upsert).toHaveBeenCalledWith("worker-1", "worker", false); - expect(mocks.monitorsRepository.findAll).not.toHaveBeenCalled(); + expect(mocks.monitorsRepository.findAllForScheduling).not.toHaveBeenCalled(); expect(mocks.jobsRepository.upsertJob).not.toHaveBeenCalled(); expect(mocks.jobsRepository.upsertCleanupJob).not.toHaveBeenCalled(); });