update tests
This commit is contained in:
1 parent
544502c6e5
commit
5d77b8804f
6 files changed
+59
-72
No files matched your search
@@ -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>) => {
|
||||
|
||||
Reference in new issue
Block a user