diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts index 8fe98f4c1..7260c82c7 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.ts @@ -51,7 +51,6 @@ export interface ISuperSimpleQueue { getMetrics(): Promise; getJobs(): Promise; flushQueues(): Promise<{ success: boolean }>; - obliterate(): Promise; } export class SuperSimpleQueue implements ISuperSimpleQueue { @@ -363,12 +362,4 @@ export class SuperSimpleQueue implements ISuperSimpleQueue { success: Boolean(stopRes && flushRes && initRes), }; }; - - obliterate = async () => { - this.logger.warn({ - message: "obliterate method not implemented", - service: SERVICE_NAME, - method: "obliterate", - }); - }; } diff --git a/server/src/service/infrastructure/bufferService.ts b/server/src/service/infrastructure/bufferService.ts index 31ff5b7aa..07be446c6 100755 --- a/server/src/service/infrastructure/bufferService.ts +++ b/server/src/service/infrastructure/bufferService.ts @@ -9,7 +9,6 @@ const SERVICE_NAME = "BufferService"; export interface IBufferService { addToBuffer(check: Check): void; addGeoCheckToBuffer(geoCheck: GeoCheck): void; - removeCheckFromBuffer(check: Check): boolean; scheduleNextFlush(): void; flushBuffer(): Promise; flushGeoBuffer(): Promise; @@ -72,44 +71,6 @@ export class BufferService implements IBufferService { } } - removeCheckFromBuffer(checkToRemove: Check) { - try { - if (!checkToRemove) { - return false; - } - - const index = this.buffer.findIndex((check) => { - if (checkToRemove.id && check.id) { - return check.id.toString() === checkToRemove.id.toString(); - } - return ( - check.metadata.monitorId.toString() === checkToRemove.metadata.monitorId && - check.metadata.teamId.toString() === checkToRemove.metadata.teamId && - check.metadata.type === checkToRemove.metadata.type && - check.status === checkToRemove.status && - check.statusCode === checkToRemove.statusCode && - check.responseTime === checkToRemove.responseTime && - check.message === checkToRemove.message - ); - }); - - if (index !== -1) { - this.buffer.splice(index, 1); - return true; - } - - return false; - } catch (error: unknown) { - this.logger.error({ - message: error instanceof Error ? error.message : "Unknown error", - service: this.SERVICE_NAME, - method: "removeCheckFromBuffer", - stack: error instanceof Error ? error.stack : undefined, - }); - return false; - } - } - scheduleNextFlush() { if (this.bufferTimer) { clearTimeout(this.bufferTimer); diff --git a/server/src/service/infrastructure/statusService.ts b/server/src/service/infrastructure/statusService.ts index 1ade8f1f7..9826d6603 100755 --- a/server/src/service/infrastructure/statusService.ts +++ b/server/src/service/infrastructure/statusService.ts @@ -362,27 +362,4 @@ export class StatusService implements IStatusService { }); } }; - - insertCheck = async (check: Check) => { - try { - if (typeof check === "undefined") { - this.logger.warn({ - message: "Failed to build check", - service: SERVICE_NAME, - method: "insertCheck", - }); - return false; - } - this.buffer.addToBuffer(check); - return true; - } catch (error: unknown) { - this.logger.error({ - message: error instanceof Error ? error.message : "Unknown error", - service: SERVICE_NAME, - method: "insertCheck", - details: { msg: `Error inserting check for monitor: ${check?.metadata.monitorId}` }, - stack: error instanceof Error ? error.stack : undefined, - }); - } - }; } diff --git a/server/src/service/system/settingsService.ts b/server/src/service/system/settingsService.ts index f2daff550..6638bc493 100755 --- a/server/src/service/system/settingsService.ts +++ b/server/src/service/system/settingsService.ts @@ -53,9 +53,6 @@ export class SettingsService implements ISettingsService { } getSettings() { - if (!this.settings) { - throw new Error("Settings have not been loaded"); - } return this.settings; } diff --git a/server/test/unit/services/bufferService.test.ts b/server/test/unit/services/bufferService.test.ts index 6bc3dfc23..7a5ee5b88 100644 --- a/server/test/unit/services/bufferService.test.ts +++ b/server/test/unit/services/bufferService.test.ts @@ -212,125 +212,6 @@ describe("BufferService", () => { }); }); - // ── removeCheckFromBuffer ──────────────────────────────────────────── - - describe("removeCheckFromBuffer", () => { - it("removes a check by id and returns true", () => { - const { service } = createService(); - const check = makeCheck({ id: "c1" }); - service.addToBuffer(check); - - const result = service.removeCheckFromBuffer(check); - - expect(result).toBe(true); - }); - - it("returns false when check is not found", () => { - const { service } = createService(); - service.addToBuffer(makeCheck({ id: "c1" })); - - const result = service.removeCheckFromBuffer(makeCheck({ id: "c999" })); - - expect(result).toBe(false); - }); - - it("returns false when passed a falsy value", () => { - const { service } = createService(); - - const result = service.removeCheckFromBuffer(null as any); - - expect(result).toBe(false); - }); - - it("matches by metadata fields when ids are missing", async () => { - const { service, checkService } = createService(); - const check = makeCheck({ - id: undefined as any, - metadata: { monitorId: "mon-1", teamId: "team-1", type: "http" } as any, - status: true, - statusCode: 200, - responseTime: 50, - message: "OK", - }); - service.addToBuffer(check); - - const result = service.removeCheckFromBuffer(check); - - expect(result).toBe(true); - await service.flushBuffer(); - expect(checkService.createChecks).not.toHaveBeenCalled(); - }); - - it("does not match when metadata fields differ", () => { - const { service } = createService(); - const check = makeCheck({ - id: undefined as any, - metadata: { monitorId: "mon-1", teamId: "team-1", type: "http" } as any, - status: true, - statusCode: 200, - responseTime: 50, - message: "OK", - }); - service.addToBuffer(check); - - const different = makeCheck({ - id: undefined as any, - metadata: { monitorId: "mon-1", teamId: "team-1", type: "http" } as any, - status: false, - statusCode: 500, - responseTime: 50, - message: "Error", - }); - const result = service.removeCheckFromBuffer(different); - - expect(result).toBe(false); - }); - - it("logs error and returns false on exception", () => { - const { service, logger } = createService(); - // Force findIndex to throw - Object.defineProperty(service, "buffer", { - value: { - findIndex: () => { - throw new Error("findIndex failed"); - }, - }, - }); - - const result = service.removeCheckFromBuffer(makeCheck()); - - expect(result).toBe(false); - expect(logger.error).toHaveBeenCalledWith( - expect.objectContaining({ - message: "findIndex failed", - service: "BufferService", - method: "removeCheckFromBuffer", - }) - ); - }); - - it("logs 'Unknown error' for non-Error thrown values in removeCheckFromBuffer", () => { - const { service, logger } = createService(); - Object.defineProperty(service, "buffer", { - value: { - findIndex: () => { - throw null; - }, - }, - }); - - const result = service.removeCheckFromBuffer(makeCheck()); - - expect(result).toBe(false); - expect(logger.error).toHaveBeenCalledWith( - expect.objectContaining({ - message: "Unknown error", - stack: undefined, - }) - ); - }); - }); - // ── scheduleNextFlush ──────────────────────────────────────────────── describe("scheduleNextFlush", () => { diff --git a/server/test/unit/services/settingsService.test.ts b/server/test/unit/services/settingsService.test.ts new file mode 100644 index 000000000..5c4935deb --- /dev/null +++ b/server/test/unit/services/settingsService.test.ts @@ -0,0 +1,184 @@ +import { describe, expect, it, jest } from "@jest/globals"; +import { SettingsService } from "../../../src/service/system/settingsService.ts"; +import type { ISettingsRepository } from "../../../src/repositories/settings/ISettingsRepository.ts"; +import type { ValidatedEnv } from "../../../src/validation/envValidation.ts"; +import type { Settings } from "../../../src/types/index.ts"; + +// ── Helpers ────────────────────────────────────────────────────────────────── + +const makeEnv = (overrides?: Partial): ValidatedEnv => + ({ + JWT_SECRET: "test-secret", + TOKEN_TTL: "99d", + NODE_ENV: "development", + LOG_LEVEL: "debug", + CLIENT_HOST: "http://localhost:5173", + DB_CONNECTION_STRING: "mongodb://localhost:27017/test_db", + DB_TYPE: "mongodb", + ...overrides, + }) as ValidatedEnv; + +const makeSettings = (overrides?: Partial): Settings => + ({ + id: "settings-1", + checkTTL: 30, + language: "en", + systemEmailSecure: false, + systemEmailPool: false, + systemEmailIgnoreTLS: false, + systemEmailRequireTLS: false, + systemEmailRejectUnauthorized: false, + showURL: true, + singleton: true, + version: 1, + createdAt: "2026-01-01T00:00:00Z", + updatedAt: "2026-01-01T00:00:00Z", + ...overrides, + }) as Settings; + +const createSettingsRepo = () => + ({ + create: jest.fn().mockResolvedValue(makeSettings()), + findSingleton: jest.fn().mockResolvedValue(makeSettings()), + update: jest.fn().mockResolvedValue(makeSettings()), + deleteLegacy: jest.fn().mockResolvedValue(true), + }) as unknown as jest.Mocked; + +const createService = (envOverrides?: Partial) => { + const env = makeEnv(envOverrides); + const service = new SettingsService(env); + const settingsRepository = createSettingsRepo(); + service.setRepository(settingsRepository); + return { service, settingsRepository, env }; +}; + +// ── Tests ──────────────────────────────────────────────────────────────────── + +describe("SettingsService", () => { + describe("serviceName", () => { + it("returns SettingsService from static property", () => { + expect(SettingsService.SERVICE_NAME).toBe("SettingsService"); + }); + + it("returns SettingsService from instance getter", () => { + const { service } = createService(); + expect(service.serviceName).toBe("SettingsService"); + }); + }); + + // ── loadSettings ──────────────────────────────────────────────────────── + + describe("loadSettings", () => { + it("returns env config mapped from constructor", () => { + const { service } = createService(); + const settings = service.loadSettings(); + + expect(settings).toEqual({ + jwtSecret: "test-secret", + jwtTTL: "99d", + nodeEnv: "development", + logLevel: "debug", + clientHost: "http://localhost:5173", + dbConnectionString: "mongodb://localhost:27017/test_db", + dbType: "mongodb", + }); + }); + + it("reflects custom env values", () => { + const { service } = createService({ NODE_ENV: "production", LOG_LEVEL: "error" }); + const settings = service.loadSettings(); + + expect(settings.nodeEnv).toBe("production"); + expect(settings.logLevel).toBe("error"); + }); + }); + + // ── getSettings ───────────────────────────────────────────────────────── + + describe("getSettings", () => { + it("returns the same config as loadSettings", () => { + const { service } = createService(); + + expect(service.getSettings()).toEqual(service.loadSettings()); + }); + }); + + // ── setRepository ─────────────────────────────────────────────────────── + + describe("setRepository", () => { + it("allows DB methods to work after being called", async () => { + const env = makeEnv(); + const service = new SettingsService(env); + const repo = createSettingsRepo(); + + service.setRepository(repo); + const settings = await service.getDBSettings(); + + expect(settings).toEqual(makeSettings()); + }); + }); + + // ── getDBSettings ─────────────────────────────────────────────────────── + + describe("getDBSettings", () => { + it("deletes legacy settings and returns singleton", async () => { + const { service, settingsRepository } = createService(); + const expected = makeSettings(); + (settingsRepository.findSingleton as jest.Mock).mockResolvedValue(expected); + + const result = await service.getDBSettings(); + + expect(settingsRepository.deleteLegacy).toHaveBeenCalled(); + expect(settingsRepository.findSingleton).toHaveBeenCalled(); + expect(result).toBe(expected); + }); + + it("creates default settings when singleton is null, then returns them", async () => { + const { service, settingsRepository } = createService(); + const created = makeSettings(); + (settingsRepository.findSingleton as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(created); + + const result = await service.getDBSettings(); + + expect(settingsRepository.create).toHaveBeenCalledWith({}); + expect(settingsRepository.findSingleton).toHaveBeenCalledTimes(2); + expect(result).toBe(created); + }); + + it("throws when settings are still null after creation attempt", async () => { + const { service, settingsRepository } = createService(); + (settingsRepository.findSingleton as jest.Mock).mockResolvedValue(null); + + await expect(service.getDBSettings()).rejects.toThrow("Settings not found"); + }); + + it("throws when repository is not set", async () => { + const env = makeEnv(); + const service = new SettingsService(env); + + await expect(service.getDBSettings()).rejects.toThrow("Settings repository not initialized"); + }); + }); + + // ── updateDbSettings ──────────────────────────────────────────────────── + + describe("updateDbSettings", () => { + it("delegates to repository and returns updated settings", async () => { + const updated = makeSettings({ checkTTL: 60 }); + const { service, settingsRepository } = createService(); + (settingsRepository.update as jest.Mock).mockResolvedValue(updated); + + const result = await service.updateDbSettings({ checkTTL: 60 }); + + expect(result).toBe(updated); + expect(settingsRepository.update).toHaveBeenCalledWith({ checkTTL: 60 }); + }); + + it("throws when repository is not set", async () => { + const env = makeEnv(); + const service = new SettingsService(env); + + await expect(service.updateDbSettings({ checkTTL: 60 })).rejects.toThrow("Settings repository not initialized"); + }); + }); +}); diff --git a/server/test/unit/services/statusService.test.ts b/server/test/unit/services/statusService.test.ts index 9a0eeb187..f1cfa53d5 100644 --- a/server/test/unit/services/statusService.test.ts +++ b/server/test/unit/services/statusService.test.ts @@ -810,80 +810,4 @@ describe("StatusService", () => { }); }); }); - - // ── insertCheck ────────────────────────────────────────────────────────── - - describe("insertCheck", () => { - it("adds check to buffer and returns true", async () => { - const { service, buffer } = createService(); - const check = makeCheck(); - - const result = await service.insertCheck(check); - - expect(result).toBe(true); - expect(buffer.addToBuffer).toHaveBeenCalledWith(check); - }); - - it("returns false and logs warning when check is undefined", async () => { - const { service, logger } = createService(); - - const result = await service.insertCheck(undefined as any); - - expect(result).toBe(false); - expect(logger.warn).toHaveBeenCalledWith( - expect.objectContaining({ - message: "Failed to build check", - service: "StatusService", - method: "insertCheck", - }) - ); - }); - - it("logs error when buffer throws", async () => { - const buffer = createBuffer(); - (buffer.addToBuffer as jest.Mock).mockImplementation(() => { - throw new Error("buffer full"); - }); - const { service, logger } = createService({ buffer }); - - const result = await service.insertCheck(makeCheck()); - - expect(result).toBeUndefined(); - expect(logger.error).toHaveBeenCalledWith( - expect.objectContaining({ - message: "buffer full", - service: "StatusService", - method: "insertCheck", - }) - ); - }); - - it("handles nullish check in error path", async () => { - const buffer = createBuffer(); - (buffer.addToBuffer as jest.Mock).mockImplementation(() => { - throw new Error("boom"); - }); - const { service, logger } = createService({ buffer }); - - await service.insertCheck(null as any); - - expect(logger.error).toHaveBeenCalledWith( - expect.objectContaining({ - details: { msg: "Error inserting check for monitor: undefined" }, - }) - ); - }); - - it("logs 'Unknown error' for non-Error exceptions in insertCheck", async () => { - const buffer = createBuffer(); - (buffer.addToBuffer as jest.Mock).mockImplementation(() => { - throw "string error"; - }); - const { service, logger } = createService({ buffer }); - - await service.insertCheck(makeCheck()); - - expect(logger.error).toHaveBeenCalledWith(expect.objectContaining({ message: "Unknown error", stack: undefined })); - }); - }); }); diff --git a/server/test/unit/services/superSimpleQueue.test.ts b/server/test/unit/services/superSimpleQueue.test.ts index 574ef6dd0..93563dd00 100644 --- a/server/test/unit/services/superSimpleQueue.test.ts +++ b/server/test/unit/services/superSimpleQueue.test.ts @@ -535,16 +535,6 @@ describe("SuperSimpleQueue", () => { }); }); - // ── obliterate ─────────────────────────────────────────────────────────── - - describe("obliterate", () => { - it("logs not-implemented warning", async () => { - const { queue, logger } = createQueue(); - await queue.obliterate(); - expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "obliterate method not implemented" })); - }); - }); - // ── static create ──────────────────────────────────────────────────────── describe("static create", () => {