Files
Checkmate/server/test/unit/services/geoChecksService.test.ts
2026-07-24 12:28:45 -07:00

259 lines
9.3 KiB
TypeScript

import { describe, expect, it, jest } from "@jest/globals";
import { GeoChecksService } from "../../../src/domain/geo-checks/geo-check.service.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import type { GeoCheckResult } from "../../../src/domain/geo-checks/geo-check.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
({
id: "mon-1",
teamId: "team-1",
type: "http",
url: "https://example.com",
geoCheckEnabled: true,
geoCheckLocations: ["NA", "EU"],
...overrides,
}) as Monitor;
const makeGeoResult = (overrides?: Partial<GeoCheckResult>): GeoCheckResult => ({
continent: "NA",
country: "US",
city: "New York",
status: "up",
responseTime: 50,
...overrides,
});
const createService = (overrides?: Record<string, unknown>) => {
const logger = createMockLogger();
const geoChecksRepository = {
createGeoChecks: jest.fn().mockResolvedValue([]),
findByMonitorId: jest.fn().mockResolvedValue({ geoChecksCount: 0, geoChecks: [] }),
};
const globalPingService = {
createMeasurement: jest.fn().mockResolvedValue("measurement-123"),
pollForResults: jest.fn().mockResolvedValue([makeGeoResult()]),
};
const monitorsRepository = {
findById: jest.fn().mockResolvedValue(makeMonitor()),
};
const defaults = { logger, geoChecksRepository, globalPingService, monitorsRepository, ...overrides };
const service = new GeoChecksService(defaults as any);
return { service, ...defaults };
};
// ── Tests ────────────────────────────────────────────────────────────────────
describe("GeoChecksService", () => {
// ── buildGeoCheck ───────────────────────────────────────────────────────
describe("buildGeoCheck", () => {
it("returns a geo check document on success", async () => {
const { service } = createService();
const result = await service.buildGeoCheck(makeMonitor());
expect(result).not.toBeNull();
expect(result!.metadata).toEqual({ monitorId: "mon-1", teamId: "team-1", type: "http" });
expect(result!.results).toHaveLength(1);
expect(result!.results[0].continent).toBe("NA");
expect(result!.id).toBeDefined();
expect(result!.expiry).toBeDefined();
});
it("returns null and warns when monitor has no URL", async () => {
const { service, logger } = createService();
const result = await service.buildGeoCheck(makeMonitor({ url: "" }));
expect(result).toBeNull();
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "Monitor missing URL for geo check" }));
});
it("returns null and warns when geoCheckLocations is empty", async () => {
const { service, logger } = createService();
const result = await service.buildGeoCheck(makeMonitor({ geoCheckLocations: [] }));
expect(result).toBeNull();
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "Monitor missing geo check locations" }));
});
it("returns null and warns when geoCheckLocations is undefined", async () => {
const { service, logger } = createService();
const result = await service.buildGeoCheck(makeMonitor({ geoCheckLocations: undefined as any }));
expect(result).toBeNull();
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "Monitor missing geo check locations" }));
});
it("returns null when createMeasurement returns null (API unavailable)", async () => {
const { service, logger } = createService({
globalPingService: {
createMeasurement: jest.fn().mockResolvedValue(null),
pollForResults: jest.fn(),
},
});
const result = await service.buildGeoCheck(makeMonitor());
expect(result).toBeNull();
expect(logger.debug).toHaveBeenCalledWith(expect.objectContaining({ message: "Skipping geo check due to API unavailability" }));
});
it("returns null when pollForResults returns empty array", async () => {
const { service, logger } = createService({
globalPingService: {
createMeasurement: jest.fn().mockResolvedValue("measurement-123"),
pollForResults: jest.fn().mockResolvedValue([]),
},
});
const result = await service.buildGeoCheck(makeMonitor());
expect(result).toBeNull();
expect(logger.debug).toHaveBeenCalledWith(expect.objectContaining({ message: "No successful geo check results" }));
});
it("returns null and logs error when an exception is thrown", async () => {
const { service, logger } = createService({
globalPingService: {
createMeasurement: jest.fn().mockRejectedValue(new Error("API error")),
pollForResults: jest.fn(),
},
});
const result = await service.buildGeoCheck(makeMonitor());
expect(result).toBeNull();
expect(logger.error).toHaveBeenCalledWith(
expect.objectContaining({
message: "Error executing geo check",
details: expect.objectContaining({ error: "API error" }),
})
);
});
it("logs 'Unknown error' for non-Error exceptions", async () => {
const { service, logger } = createService({
globalPingService: {
createMeasurement: jest.fn().mockRejectedValue("string error"),
pollForResults: jest.fn(),
},
});
const result = await service.buildGeoCheck(makeMonitor());
expect(result).toBeNull();
expect(logger.error).toHaveBeenCalledWith(expect.objectContaining({ details: expect.objectContaining({ error: "Unknown error" }) }));
});
});
// ── createGeoChecks ─────────────────────────────────────────────────────
describe("createGeoChecks", () => {
it("delegates to repository", async () => {
const { service, geoChecksRepository } = createService();
const geoChecks = [{ id: "gc-1" }] as any;
await service.createGeoChecks(geoChecks);
expect(geoChecksRepository.createGeoChecks).toHaveBeenCalledWith(geoChecks);
});
});
// ── getGeoChecksByMonitor ────────────────────────────────────────────────
describe("getGeoChecksByMonitor", () => {
it("returns geo checks from repository", async () => {
const expected = { geoChecksCount: 1, geoChecks: [{ id: "gc-1" }] };
const { service, geoChecksRepository } = createService();
(geoChecksRepository.findByMonitorId as jest.Mock).mockResolvedValue(expected);
const result = await service.getGeoChecksByMonitor({
monitorId: "mon-1",
teamId: "team-1",
sortOrder: "desc",
dateRange: "day",
continent: "NA" as any,
});
expect(result).toEqual(expected);
expect(geoChecksRepository.findByMonitorId).toHaveBeenCalledWith("mon-1", "desc", "day", 0, 5, ["NA"]);
});
it("passes array of continents through", async () => {
const { service, geoChecksRepository } = createService();
await service.getGeoChecksByMonitor({
monitorId: "mon-1",
teamId: "team-1",
sortOrder: "desc",
dateRange: "day",
continent: ["NA", "EU"] as any,
});
expect(geoChecksRepository.findByMonitorId).toHaveBeenCalledWith("mon-1", "desc", "day", 0, 5, ["NA", "EU"]);
});
it("passes undefined continents when not provided", async () => {
const { service, geoChecksRepository } = createService();
await service.getGeoChecksByMonitor({
monitorId: "mon-1",
teamId: "team-1",
sortOrder: "desc",
dateRange: "day",
continent: undefined as any,
});
expect(geoChecksRepository.findByMonitorId).toHaveBeenCalledWith("mon-1", "desc", "day", 0, 5, undefined);
});
it("uses provided page and rowsPerPage", async () => {
const { service, geoChecksRepository } = createService();
await service.getGeoChecksByMonitor({
monitorId: "mon-1",
teamId: "team-1",
sortOrder: "asc",
dateRange: "week",
page: 3,
rowsPerPage: 20,
continent: "EU" as any,
});
expect(geoChecksRepository.findByMonitorId).toHaveBeenCalledWith("mon-1", "asc", "week", 3, 20, ["EU"]);
});
it("throws when monitorId is missing", async () => {
const { service } = createService();
await expect(
service.getGeoChecksByMonitor({ monitorId: "", teamId: "team-1", sortOrder: "desc", dateRange: "day", continent: "NA" as any })
).rejects.toThrow("No monitor ID in request");
});
it("throws when teamId is missing", async () => {
const { service } = createService();
await expect(
service.getGeoChecksByMonitor({ monitorId: "mon-1", teamId: "", sortOrder: "desc", dateRange: "day", continent: "NA" as any })
).rejects.toThrow("No team ID in request");
});
it("throws 404 when monitor is not found", async () => {
const { service, monitorsRepository } = createService();
(monitorsRepository.findById as jest.Mock).mockResolvedValue(null);
await expect(
service.getGeoChecksByMonitor({ monitorId: "mon-1", teamId: "team-1", sortOrder: "desc", dateRange: "day", continent: "NA" as any })
).rejects.toThrow("Monitor with ID mon-1 not found.");
});
});
});