Files
Checkmate/server/test/unit/services/maintenanceWindowService.test.ts
T

253 lines
10 KiB
TypeScript

import { describe, expect, it, jest } from "@jest/globals";
import { MaintenanceWindowService } from "../../../src/service/business/maintenanceWindowService.ts";
import type { IMaintenanceWindowsRepository } from "../../../src/repositories/maintenance-windows/IMaintenanceWindowsRepository.ts";
import type { IMonitorsRepository } from "../../../src/repositories/monitors/IMonitorsRepository.ts";
import type { MaintenanceWindow } from "../../../src/types/index.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
const createMaintenanceWindowsRepo = () =>
({
create: jest.fn().mockResolvedValue(makeWindow()),
findById: jest.fn().mockResolvedValue(makeWindow()),
findByMonitorId: jest.fn().mockResolvedValue([makeWindow()]),
findByTeamId: jest.fn().mockResolvedValue([makeWindow()]),
updateById: jest.fn().mockResolvedValue(makeWindow()),
deleteById: jest.fn().mockResolvedValue(makeWindow()),
countByTeamId: jest.fn().mockResolvedValue(1),
}) as unknown as jest.Mocked<IMaintenanceWindowsRepository>;
const createMonitorsRepo = () =>
({
findByIds: jest.fn().mockResolvedValue([
{ id: "mon-1", teamId: "team-1" },
{ id: "mon-2", teamId: "team-1" },
]),
}) as unknown as jest.Mocked<IMonitorsRepository>;
const createService = (overrides?: {
monitorsRepository?: ReturnType<typeof createMonitorsRepo>;
maintenanceWindowsRepository?: ReturnType<typeof createMaintenanceWindowsRepo>;
}) => {
const monitorsRepository = overrides?.monitorsRepository ?? createMonitorsRepo();
const maintenanceWindowsRepository = overrides?.maintenanceWindowsRepository ?? createMaintenanceWindowsRepo();
const service = new MaintenanceWindowService({ monitorsRepository, maintenanceWindowsRepository });
return { service, monitorsRepository, maintenanceWindowsRepository };
};
const makeWindow = (overrides?: Partial<MaintenanceWindow>): MaintenanceWindow => ({
id: "mw-1",
monitorId: "mon-1",
teamId: "team-1",
active: true,
name: "Scheduled Maintenance",
duration: 60,
durationUnit: "minutes",
repeat: 0,
start: "2026-04-10T02:00:00Z",
end: "2026-04-10T03:00:00Z",
createdAt: "2026-01-01T00:00:00Z",
updatedAt: "2026-01-01T00:00:00Z",
...overrides,
});
const defaultCreateParams = {
teamId: "team-1",
monitorIDs: ["mon-1", "mon-2"],
name: "Scheduled Maintenance",
active: true,
duration: 60,
durationUnit: "minutes" as const,
repeat: 0,
start: "2026-04-10T02:00:00Z",
end: "2026-04-10T03:00:00Z",
};
// ── Tests ────────────────────────────────────────────────────────────────────
describe("MaintenanceWindowService", () => {
describe("serviceName", () => {
it("returns maintenanceWindowService from static property", () => {
expect(MaintenanceWindowService.SERVICE_NAME).toBe("maintenanceWindowService");
});
it("returns maintenanceWindowService from instance getter", () => {
const { service } = createService();
expect(service.serviceName).toBe("maintenanceWindowService");
});
});
// ── createMaintenanceWindow ──────────────────────────────────────────────
describe("createMaintenanceWindow", () => {
it("creates a maintenance window for each monitor", async () => {
const { service, maintenanceWindowsRepository } = createService();
await service.createMaintenanceWindow(defaultCreateParams);
expect(maintenanceWindowsRepository.create).toHaveBeenCalledTimes(2);
expect(maintenanceWindowsRepository.create).toHaveBeenCalledWith(
expect.objectContaining({
teamId: "team-1",
monitorId: "mon-1",
name: "Scheduled Maintenance",
active: true,
duration: 60,
durationUnit: "minutes",
repeat: 0,
start: "2026-04-10T02:00:00Z",
end: "2026-04-10T03:00:00Z",
})
);
expect(maintenanceWindowsRepository.create).toHaveBeenCalledWith(expect.objectContaining({ monitorId: "mon-2" }));
});
it("verifies monitor ownership via findByIds", async () => {
const { service, monitorsRepository } = createService();
await service.createMaintenanceWindow(defaultCreateParams);
expect(monitorsRepository.findByIds).toHaveBeenCalledWith(["mon-1", "mon-2"]);
});
it("throws 403 when a monitor belongs to a different team", async () => {
const monitorsRepository = createMonitorsRepo();
(monitorsRepository.findByIds as jest.Mock).mockResolvedValue([
{ id: "mon-1", teamId: "team-1" },
{ id: "mon-2", teamId: "team-other" },
]);
const { service, maintenanceWindowsRepository } = createService({ monitorsRepository });
await expect(service.createMaintenanceWindow(defaultCreateParams)).rejects.toThrow(
"Unauthorized to create maintenance window for one or more monitors"
);
expect(maintenanceWindowsRepository.create).not.toHaveBeenCalled();
});
it("does not throw when all monitors belong to the team", async () => {
const { service } = createService();
await expect(service.createMaintenanceWindow(defaultCreateParams)).resolves.toBeUndefined();
});
it("creates a single window when monitorIDs has one entry", async () => {
const { service, maintenanceWindowsRepository } = createService();
await service.createMaintenanceWindow({ ...defaultCreateParams, monitorIDs: ["mon-1"] });
expect(maintenanceWindowsRepository.create).toHaveBeenCalledTimes(1);
});
});
// ── getMaintenanceWindowById ─────────────────────────────────────────────
describe("getMaintenanceWindowById", () => {
it("delegates to repository with id and teamId", async () => {
const expected = makeWindow();
const { service, maintenanceWindowsRepository } = createService();
(maintenanceWindowsRepository.findById as jest.Mock).mockResolvedValue(expected);
const result = await service.getMaintenanceWindowById({ id: "mw-1", teamId: "team-1" });
expect(result).toBe(expected);
expect(maintenanceWindowsRepository.findById).toHaveBeenCalledWith("mw-1", "team-1");
});
});
// ── getMaintenanceWindowsByTeamId ────────────────────────────────────────
describe("getMaintenanceWindowsByTeamId", () => {
it("returns windows and count", async () => {
const windows = [makeWindow()];
const { service, maintenanceWindowsRepository } = createService();
(maintenanceWindowsRepository.findByTeamId as jest.Mock).mockResolvedValue(windows);
(maintenanceWindowsRepository.countByTeamId as jest.Mock).mockResolvedValue(1);
const result = await service.getMaintenanceWindowsByTeamId({ teamId: "team-1" });
expect(result).toEqual({ maintenanceWindows: windows, maintenanceWindowCount: 1 });
});
it("defaults page to 0 and rowsPerPage to 10 when not provided", async () => {
const { service, maintenanceWindowsRepository } = createService();
await service.getMaintenanceWindowsByTeamId({ teamId: "team-1" });
expect(maintenanceWindowsRepository.findByTeamId).toHaveBeenCalledWith("team-1", 0, 10, undefined, undefined, undefined);
});
it("passes pagination and filter parameters through", async () => {
const { service, maintenanceWindowsRepository } = createService();
await service.getMaintenanceWindowsByTeamId({
teamId: "team-1",
active: true,
page: 2,
rowsPerPage: 5,
field: "name",
order: "asc",
});
expect(maintenanceWindowsRepository.findByTeamId).toHaveBeenCalledWith("team-1", 2, 5, "name", "asc", true);
expect(maintenanceWindowsRepository.countByTeamId).toHaveBeenCalledWith("team-1", true);
});
});
// ── getMaintenanceWindowsByMonitorId ─────────────────────────────────────
describe("getMaintenanceWindowsByMonitorId", () => {
it("delegates to repository with monitorId and teamId", async () => {
const windows = [makeWindow()];
const { service, maintenanceWindowsRepository } = createService();
(maintenanceWindowsRepository.findByMonitorId as jest.Mock).mockResolvedValue(windows);
const result = await service.getMaintenanceWindowsByMonitorId({ monitorId: "mon-1", teamId: "team-1" });
expect(result).toBe(windows);
expect(maintenanceWindowsRepository.findByMonitorId).toHaveBeenCalledWith("mon-1", "team-1");
});
});
// ── deleteMaintenanceWindow ──────────────────────────────────────────────
describe("deleteMaintenanceWindow", () => {
it("delegates to repository and returns deleted window", async () => {
const deleted = makeWindow();
const { service, maintenanceWindowsRepository } = createService();
(maintenanceWindowsRepository.deleteById as jest.Mock).mockResolvedValue(deleted);
const result = await service.deleteMaintenanceWindow({ id: "mw-1", teamId: "team-1" });
expect(result).toBe(deleted);
expect(maintenanceWindowsRepository.deleteById).toHaveBeenCalledWith("mw-1", "team-1");
});
});
// ── editMaintenanceWindow ────────────────────────────────────────────────
describe("editMaintenanceWindow", () => {
it("delegates to repository with id, teamId, and body", async () => {
const updated = makeWindow({ name: "Updated Name" });
const { service, maintenanceWindowsRepository } = createService();
(maintenanceWindowsRepository.updateById as jest.Mock).mockResolvedValue(updated);
const result = await service.editMaintenanceWindow({ id: "mw-1", teamId: "team-1", body: { name: "Updated Name" } });
expect(result).toBe(updated);
expect(maintenanceWindowsRepository.updateById).toHaveBeenCalledWith("mw-1", "team-1", { name: "Updated Name" });
});
it("passes partial body fields through", async () => {
const { service, maintenanceWindowsRepository } = createService();
await service.editMaintenanceWindow({
id: "mw-1",
teamId: "team-1",
body: { active: false, repeat: 3600000 },
});
expect(maintenanceWindowsRepository.updateById).toHaveBeenCalledWith("mw-1", "team-1", { active: false, repeat: 3600000 });
});
});
});