update tests

This commit is contained in:
Alex Holliday committed 2026-09-24 14:22:31 -07:00
1 parent 544502c6e5
commit 5d77b8804f
6 files changed
+59 -72

No files matched your search

+2 -2
View File
@@ -63,7 +63,7 @@ export interface HeartbeatTestHarness {
networkService: { requestStatus: jest.Mock };
bufferStub: { addToBuffer: jest.Mock; addGeoCheckToBuffer: jest.Mock; scheduleNextFlush: jest.Mock };
maintenanceWindowsRepo: { findByMonitorId: jest.Mock };
messageBuilder: { extractThresholdBreaches: jest.Mock };
messageBuilder: { buildThresholdBreachMessage: jest.Mock };
egressService: { assessAfterFailure: jest.Mock };
heartbeatJob: (monitor: Monitor) => Promise<void>;
setNextResponse: (status: boolean, code: number) => void;
@@ -79,7 +79,7 @@ export function createHeartbeatTestHarness(): HeartbeatTestHarness {
const statusService = new StatusService(logger, monitorsRepo as any, createStubMonitorStatsRepo() as any);
const messageBuilder = { extractThresholdBreaches: jest.fn().mockReturnValue([]) };
const messageBuilder = { buildThresholdBreachMessage: jest.fn().mockReturnValue("") };
const incidentService = new IncidentService(logger, incidentsRepo, monitorsRepo as any, { findById: jest.fn() } as any, messageBuilder as any);
const notificationsService = { handleNotifications: jest.fn().mockResolvedValue(true) };
@@ -49,7 +49,7 @@ describe("Heartbeat job: hardware threshold breach", () => {
h.setNextResponseFull(makeHardwareResponse(0.9));
// Configure message builder for when incident is created
h.messageBuilder.extractThresholdBreaches.mockReturnValue([{ metric: "cpu", formattedValue: "90%", threshold: 80, unit: "%" }]);
h.messageBuilder.buildThresholdBreachMessage.mockReturnValue("CPU: 90% (threshold: 80%)");
// 4 heartbeats: counter goes 5→4→3→2→1, status still "up"
for (let i = 0; i < 4; i++) {
@@ -191,7 +191,7 @@ describe("Heartbeat job: hardware threshold breach", () => {
h.monitorsRepo.seed(monitor);
h.setNextResponseFull(makeHardwareResponse(0.9));
h.messageBuilder.extractThresholdBreaches.mockReturnValue([{ metric: "cpu", formattedValue: "90%", threshold: 80, unit: "%" }]);
h.messageBuilder.buildThresholdBreachMessage.mockReturnValue("CPU: 90% (threshold: 80%)");
// 5 heartbeats to trigger breach, then 5 more while still breaching
for (let i = 0; i < 10; i++) {
@@ -55,7 +55,7 @@ describe("Incident lifecycle (integration)", () => {
repo = new InMemoryIncidentsRepository();
monitorsRepo = { findById: jest.fn() } as unknown as jest.Mocked<IMonitorsRepository>;
usersRepo = { findById: jest.fn() } as unknown as jest.Mocked<IUsersRepository>;
messageBuilder = { extractThresholdBreaches: jest.fn() } as unknown as jest.Mocked<INotificationMessageBuilder>;
messageBuilder = { buildThresholdBreachMessage: jest.fn() } as unknown as jest.Mocked<INotificationMessageBuilder>;
service = new IncidentService(createMockLogger() as any, repo, monitorsRepo, usersRepo, messageBuilder);
});
@@ -152,10 +152,7 @@ describe("Incident lifecycle (integration)", () => {
it("creates a threshold breach incident with statusCode 9999 and descriptive message", async () => {
const monitor = makeMonitor({ status: "breached", type: "hardware" });
(messageBuilder.extractThresholdBreaches as jest.Mock).mockReturnValue([
{ metric: "cpu", formattedValue: "92%", threshold: 80, unit: "%" },
{ metric: "memory", formattedValue: "88%", threshold: 85, unit: "%" },
]);
(messageBuilder.buildThresholdBreachMessage as jest.Mock).mockReturnValue("CPU: 92% (threshold: 80%), MEMORY: 88% (threshold: 85%)");
const decision = makeDecision({ shouldCreateIncident: true, incidentReason: "threshold_breach" });
const incident = await service.handleIncident(monitor, decision, makeCheck(200));
@@ -36,7 +36,7 @@ const createUsersRepo = () =>
const createMessageBuilder = () =>
({
extractThresholdBreaches: jest.fn(),
buildThresholdBreachMessage: jest.fn(),
}) as unknown as jest.Mocked<INotificationMessageBuilder>;
const createService = (overrides?: {
@@ -131,7 +131,7 @@ describe("IncidentService", () => {
it("creates a new incident when shouldCreateIncident and no active incident exists", async () => {
const created = makeIncident();
const { service, incidentsRepository } = createService();
const { service, incidentsRepository, notificationMessageBuilder } = createService();
(incidentsRepository.findActiveByMonitorId as jest.Mock).mockResolvedValue(null);
(incidentsRepository.create as jest.Mock).mockResolvedValue(created);
@@ -150,6 +150,7 @@ describe("IncidentService", () => {
statusCode: 500,
})
);
expect(notificationMessageBuilder.buildThresholdBreachMessage).not.toHaveBeenCalled();
});
it("uses status code 9999 and builds message for threshold_breach incidents", async () => {
@@ -157,9 +158,7 @@ describe("IncidentService", () => {
const { service, incidentsRepository, notificationMessageBuilder } = createService();
(incidentsRepository.findActiveByMonitorId as jest.Mock).mockResolvedValue(null);
(incidentsRepository.create as jest.Mock).mockResolvedValue(created);
(notificationMessageBuilder.extractThresholdBreaches as jest.Mock).mockReturnValue([
{ metric: "cpu", formattedValue: "95%", threshold: 80, unit: "%" },
]);
(notificationMessageBuilder.buildThresholdBreachMessage as jest.Mock).mockReturnValue("CPU: 95% (threshold: 80%)");
const monitor = makeMonitor({ type: "hardware" });
const check = makeCheck({ metadata: { monitorId: "mon-1", teamId: "team-1", type: "hardware" }, cpu: { usage_percent: 0.95 } });
@@ -167,7 +166,7 @@ describe("IncidentService", () => {
const decision = makeDecision({ shouldCreateIncident: true, incidentReason: "threshold_breach", thresholdBreaches });
await service.handleIncident(monitor, decision, check);
expect(notificationMessageBuilder.extractThresholdBreaches).toHaveBeenCalledWith(monitor, check, thresholdBreaches);
expect(notificationMessageBuilder.buildThresholdBreachMessage).toHaveBeenCalledWith(monitor, check, thresholdBreaches);
expect(incidentsRepository.create).toHaveBeenCalledWith(
expect.objectContaining({
statusCode: 9999,
@@ -176,62 +175,6 @@ describe("IncidentService", () => {
);
});
it("uses fallback message when extractThresholdBreaches returns empty array", async () => {
const created = makeIncident();
const { service, incidentsRepository, notificationMessageBuilder } = createService();
(incidentsRepository.findActiveByMonitorId as jest.Mock).mockResolvedValue(null);
(incidentsRepository.create as jest.Mock).mockResolvedValue(created);
(notificationMessageBuilder.extractThresholdBreaches as jest.Mock).mockReturnValue([]);
const decision = makeDecision({ shouldCreateIncident: true, incidentReason: "threshold_breach" });
await service.handleIncident(makeMonitor(), decision, makeCheck());
expect(incidentsRepository.create).toHaveBeenCalledWith(expect.objectContaining({ message: "Threshold breach detected" }));
});
it("builds the container list for docker threshold_breach incidents", async () => {
const created = makeIncident();
const { service, incidentsRepository, notificationMessageBuilder } = createService();
(incidentsRepository.findActiveByMonitorId as jest.Mock).mockResolvedValue(null);
(incidentsRepository.create as jest.Mock).mockResolvedValue(created);
const monitor = makeMonitor({ type: "docker", status: "breached", dockerAlertOnStopped: true, dockerAlertOnUnhealthy: true });
const check = makeCheck({
metadata: { monitorId: "mon-1", teamId: "team-1", type: "docker" },
containers: [
{ id: "1", name: "db", image: "postgres", state: "exited", status: "Exited (1)", health: "none", exitCode: 1 },
{ id: "2", name: "api", image: "api", state: "running", status: "Up", health: "unhealthy" },
{ id: "3", name: "web", image: "nginx", state: "running", status: "Up", health: "healthy" },
],
containerSummary: { total: 3, running: 2, stopped: 1, unhealthy: 1 },
});
const decision = makeDecision({ shouldCreateIncident: true, incidentReason: "threshold_breach" });
await service.handleIncident(monitor, decision, check);
expect(incidentsRepository.create).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: 9999, message: "db: stopped (exit code 1), api: unhealthy" })
);
expect(notificationMessageBuilder.extractThresholdBreaches).not.toHaveBeenCalled();
});
it("joins multiple threshold breaches with commas", async () => {
const created = makeIncident();
const { service, incidentsRepository, notificationMessageBuilder } = createService();
(incidentsRepository.findActiveByMonitorId as jest.Mock).mockResolvedValue(null);
(incidentsRepository.create as jest.Mock).mockResolvedValue(created);
(notificationMessageBuilder.extractThresholdBreaches as jest.Mock).mockReturnValue([
{ metric: "cpu", formattedValue: "95%", threshold: 80, unit: "%" },
{ metric: "memory", formattedValue: "90%", threshold: 80, unit: "%" },
]);
const decision = makeDecision({ shouldCreateIncident: true, incidentReason: "threshold_breach" });
await service.handleIncident(makeMonitor(), decision, makeCheck());
expect(incidentsRepository.create).toHaveBeenCalledWith(
expect.objectContaining({ message: "CPU: 95% (threshold: 80%), MEMORY: 90% (threshold: 80%)" })
);
});
it("resolves active incident when shouldResolveIncident", async () => {
const active = makeIncident();
const resolved = makeIncident({ status: false, endTime: "123", resolutionType: "automatic" });
@@ -627,4 +627,51 @@ describe("NotificationMessageBuilder", () => {
});
});
});
// ── buildThresholdBreachMessage ─────────────────────────────────────────
describe("buildThresholdBreachMessage", () => {
const none: HardwareBreaches = { cpu: false, memory: false, disk: false, temp: false };
const hardwareMonitor = () => makeMonitor({ type: "hardware", cpuAlertThreshold: 80, memoryAlertThreshold: 80 });
it("describes a single flagged metric", () => {
const check = makeCheck({ cpu: { usage_percent: 0.95, temperature: [50] } });
const message = builder.buildThresholdBreachMessage(hardwareMonitor(), check, { ...none, cpu: true });
expect(message).toBe("CPU: 95.0% (threshold: 80%)");
});
it("joins several flagged metrics with a comma", () => {
const check = makeCheck({ cpu: { usage_percent: 0.95, temperature: [50] }, memory: { usage_percent: 0.9 } });
const message = builder.buildThresholdBreachMessage(hardwareMonitor(), check, { ...none, cpu: true, memory: true });
expect(message).toBe("CPU: 95.0% (threshold: 80%), MEMORY: 90.0% (threshold: 80%)");
});
it("falls back when no metric is flagged", () => {
const check = makeCheck({ cpu: { usage_percent: 0.95, temperature: [50] } });
expect(builder.buildThresholdBreachMessage(hardwareMonitor(), check, none)).toBe("Threshold breach detected");
expect(builder.buildThresholdBreachMessage(hardwareMonitor(), check, undefined)).toBe("Threshold breach detected");
});
it("lists the breaching containers for a docker monitor", () => {
const check = makeCheck({
status: true,
containers: makeContainers([{ name: "db", state: "exited", exitCode: 1 }, { name: "api", health: "unhealthy" }, { name: "web" }]),
});
const message = builder.buildThresholdBreachMessage(makeDockerMonitor(), check, undefined);
expect(message).toBe("db: stopped (exit code 1), api: unhealthy");
});
it("falls back when no container is breaching", () => {
const check = makeCheck({ status: true, containers: makeContainers([{ name: "web" }]) });
expect(builder.buildThresholdBreachMessage(makeDockerMonitor(), check, undefined)).toBe("Container alert");
});
});
});
@@ -33,7 +33,7 @@ const createSettingsService = (clientHost = "https://app.example.com") => ({
const createMessageBuilder = () => ({
buildMessage: jest.fn().mockReturnValue({ type: "monitor_down", content: { title: "Down" } }),
extractThresholdBreaches: jest.fn(),
buildThresholdBreachMessage: jest.fn(),
});
const createService = (overrides?: Record<string, unknown>) => {