update tests
This commit is contained in:
1 parent
6b75f9bf7c
commit
11c89d150a
3 files changed
+152
-314
No files matched your search
@@ -163,10 +163,11 @@ describe("IncidentService", () => {
|
||||
|
||||
const monitor = makeMonitor({ type: "hardware" });
|
||||
const check = makeCheck({ metadata: { monitorId: "mon-1", teamId: "team-1", type: "hardware" }, cpu: { usage_percent: 0.95 } });
|
||||
const decision = makeDecision({ shouldCreateIncident: true, incidentReason: "threshold_breach" });
|
||||
const thresholdBreaches = { cpu: true, memory: false, disk: false, temp: false };
|
||||
const decision = makeDecision({ shouldCreateIncident: true, incidentReason: "threshold_breach", thresholdBreaches });
|
||||
await service.handleIncident(monitor, decision, check);
|
||||
|
||||
expect(notificationMessageBuilder.extractThresholdBreaches).toHaveBeenCalledWith(monitor, check);
|
||||
expect(notificationMessageBuilder.extractThresholdBreaches).toHaveBeenCalledWith(monitor, check, thresholdBreaches);
|
||||
expect(incidentsRepository.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
statusCode: 9999,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, beforeEach } from "@jest/globals";
|
||||
import { NotificationMessageBuilder } from "../../../src/domain/notifications/notification.message-builder.ts";
|
||||
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
|
||||
import type { HardwareBreaches, Monitor } from "../../../src/domain/monitors/monitor.type.ts";
|
||||
import type { Check } from "../../../src/domain/checks/check.type.ts";
|
||||
import type { DockerContainerInfo } from "../../../src/domain/docker/docker.type.ts";
|
||||
import type { MonitorActionDecision } from "../../../src/worker/worker.interface.ts";
|
||||
@@ -119,7 +119,10 @@ describe("NotificationMessageBuilder", () => {
|
||||
|
||||
it("builds a threshold_breach message", () => {
|
||||
const monitor = makeMonitor({ status: "up", type: "hardware", cpuAlertThreshold: 80 });
|
||||
const decision = makeDecision({ notificationReason: "threshold_breach" });
|
||||
const decision = makeDecision({
|
||||
notificationReason: "threshold_breach",
|
||||
thresholdBreaches: { cpu: true, memory: false, disk: false, temp: false },
|
||||
});
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.9, temperature: [50] } });
|
||||
|
||||
const msg = builder.buildMessage(monitor, check, decision, "https://app.example.com");
|
||||
@@ -346,21 +349,26 @@ describe("NotificationMessageBuilder", () => {
|
||||
});
|
||||
|
||||
describe("threshold_breach", () => {
|
||||
it("includes threshold breaches in content", () => {
|
||||
it("includes the breaches flagged on the decision in content", () => {
|
||||
const monitor = makeMonitor({ status: "up", type: "hardware", cpuAlertThreshold: 80 });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.9, temperature: [50] } });
|
||||
const decision = makeDecision({
|
||||
notificationReason: "threshold_breach",
|
||||
thresholdBreaches: { cpu: true, memory: false, disk: false, temp: false },
|
||||
});
|
||||
|
||||
const msg = builder.buildMessage(monitor, check, decision, "");
|
||||
|
||||
expect(msg.content.thresholds).toEqual([expect.objectContaining({ metric: "cpu", currentValue: 90, threshold: 80 })]);
|
||||
});
|
||||
|
||||
it("leaves thresholds empty when the decision carries no breaches", () => {
|
||||
const monitor = makeMonitor({ status: "up", type: "hardware", cpuAlertThreshold: 80 });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.9, temperature: [50] } });
|
||||
|
||||
const msg = builder.buildMessage(monitor, check, makeDecision({ notificationReason: "threshold_breach" }), "");
|
||||
|
||||
expect(msg.content.thresholds).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
metric: "cpu",
|
||||
currentValue: 90,
|
||||
threshold: 80,
|
||||
}),
|
||||
])
|
||||
);
|
||||
expect(msg.content.thresholds).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -480,346 +488,151 @@ describe("NotificationMessageBuilder", () => {
|
||||
// ── extractThresholdBreaches ─────────────────────────────────────────
|
||||
|
||||
describe("extractThresholdBreaches", () => {
|
||||
it("returns empty array for non-hardware monitor", () => {
|
||||
const monitor = makeMonitor({ type: "http" });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.99 } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns empty array when the check carries no hardware data", () => {
|
||||
const monitor = makeMonitor({
|
||||
const none: HardwareBreaches = { cpu: false, memory: false, disk: false, temp: false };
|
||||
const hardwareMonitor = (overrides?: Partial<Monitor>) =>
|
||||
makeMonitor({
|
||||
type: "hardware",
|
||||
cpuAlertThreshold: 80,
|
||||
memoryAlertThreshold: 80,
|
||||
diskAlertThreshold: 80,
|
||||
tempAlertThreshold: 80,
|
||||
memoryAlertThreshold: 70,
|
||||
diskAlertThreshold: 85,
|
||||
tempAlertThreshold: 65,
|
||||
...overrides,
|
||||
});
|
||||
const check = makeCheck();
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
it("returns empty when no breaches are supplied", () => {
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.99, temperature: [99] } });
|
||||
|
||||
expect(breaches).toEqual([]);
|
||||
expect(builder.extractThresholdBreaches(hardwareMonitor(), check, undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
// ── CPU ──────────────────────────────────────────────────────────
|
||||
it("returns empty when every flag is false, whatever the check says", () => {
|
||||
const check = makeCheck({
|
||||
cpu: { usage_percent: 0.99, temperature: [99] },
|
||||
memory: { usage_percent: 0.99 },
|
||||
disk: [{ usage_percent: 0.99 }],
|
||||
});
|
||||
|
||||
describe("cpu threshold", () => {
|
||||
it("detects CPU breach when usage exceeds threshold", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", cpuAlertThreshold: 80 });
|
||||
expect(builder.extractThresholdBreaches(hardwareMonitor(), check, none)).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns empty for a non-hardware monitor even when flags are set", () => {
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.99 } });
|
||||
|
||||
expect(builder.extractThresholdBreaches(makeMonitor({ type: "http" }), check, { ...none, cpu: true })).toEqual([]);
|
||||
});
|
||||
|
||||
// The flags are the verdict. The builder formats what it is told is breaching and never re-runs the comparison.
|
||||
it("reports a flagged metric without re-testing it against the threshold", () => {
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.5, temperature: [50] } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, cpu: true });
|
||||
|
||||
expect(breaches).toEqual([{ metric: "cpu", currentValue: 50, threshold: 80, unit: "%", formattedValue: "50.0%" }]);
|
||||
});
|
||||
|
||||
describe("cpu", () => {
|
||||
it("formats cpu usage as a percentage when flagged", () => {
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.9, temperature: [50] } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, cpu: true });
|
||||
|
||||
expect(breaches).toContainEqual(
|
||||
expect.objectContaining({
|
||||
metric: "cpu",
|
||||
currentValue: 90,
|
||||
threshold: 80,
|
||||
unit: "%",
|
||||
formattedValue: "90.0%",
|
||||
})
|
||||
);
|
||||
expect(breaches).toEqual([{ metric: "cpu", currentValue: 90, threshold: 80, unit: "%", formattedValue: "90.0%" }]);
|
||||
});
|
||||
|
||||
it("does not report CPU breach when usage is below threshold", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", cpuAlertThreshold: 80 });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.5, temperature: [50] } });
|
||||
it("ignores cpu when not flagged", () => {
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.9, temperature: [50] } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "cpu")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips CPU check when cpuAlertThreshold is undefined", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", cpuAlertThreshold: undefined });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.99 } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "cpu")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips CPU check when cpuAlertThreshold is null", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", cpuAlertThreshold: null as any });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.99 } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "cpu")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips CPU check when cpu usage_percent is undefined", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", cpuAlertThreshold: 80 });
|
||||
const check = makeCheck({ cpu: { usage_percent: undefined } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "cpu")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips CPU check when cpu object is undefined", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", cpuAlertThreshold: 80 });
|
||||
const check = makeCheck({ cpu: undefined });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, memory: true });
|
||||
|
||||
expect(breaches.find((b) => b.metric === "cpu")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Memory ───────────────────────────────────────────────────────
|
||||
|
||||
describe("memory threshold", () => {
|
||||
it("detects memory breach when usage exceeds threshold", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", memoryAlertThreshold: 70 });
|
||||
describe("memory", () => {
|
||||
it("formats memory usage as a percentage when flagged", () => {
|
||||
const check = makeCheck({ memory: { usage_percent: 0.85 } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, memory: true });
|
||||
|
||||
expect(breaches).toContainEqual(
|
||||
expect.objectContaining({
|
||||
metric: "memory",
|
||||
currentValue: 85,
|
||||
threshold: 70,
|
||||
unit: "%",
|
||||
formattedValue: "85.0%",
|
||||
})
|
||||
);
|
||||
expect(breaches).toEqual([{ metric: "memory", currentValue: 85, threshold: 70, unit: "%", formattedValue: "85.0%" }]);
|
||||
});
|
||||
|
||||
it("does not report memory breach when usage is below threshold", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", memoryAlertThreshold: 90 });
|
||||
const check = makeCheck({ memory: { usage_percent: 0.5 } });
|
||||
it("ignores memory when not flagged", () => {
|
||||
const check = makeCheck({ memory: { usage_percent: 0.85 } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "memory")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips memory check when memoryAlertThreshold is undefined", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", memoryAlertThreshold: undefined });
|
||||
const check = makeCheck({ memory: { usage_percent: 0.99 } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "memory")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips memory check when memoryAlertThreshold is null", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", memoryAlertThreshold: null as any });
|
||||
const check = makeCheck({ memory: { usage_percent: 0.99 } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "memory")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips memory check when memory usage_percent is undefined", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", memoryAlertThreshold: 70 });
|
||||
const check = makeCheck({ memory: { usage_percent: undefined } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "memory")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips memory check when memory object is undefined", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", memoryAlertThreshold: 70 });
|
||||
const check = makeCheck({ memory: undefined });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, cpu: true });
|
||||
|
||||
expect(breaches.find((b) => b.metric === "memory")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Disk ─────────────────────────────────────────────────────────
|
||||
|
||||
describe("disk threshold", () => {
|
||||
it("detects disk breach using highest usage across multiple disks", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", diskAlertThreshold: 80 });
|
||||
describe("disk", () => {
|
||||
it("formats the highest usage across disks when flagged", () => {
|
||||
const check = makeCheck({ disk: [{ usage_percent: 0.5 }, { usage_percent: 0.95 }, { usage_percent: 0.7 }] });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, disk: true });
|
||||
|
||||
expect(breaches).toContainEqual(
|
||||
expect.objectContaining({
|
||||
metric: "disk",
|
||||
currentValue: 95,
|
||||
threshold: 80,
|
||||
formattedValue: "95.0%",
|
||||
})
|
||||
);
|
||||
expect(breaches).toEqual([{ metric: "disk", currentValue: 95, threshold: 85, unit: "%", formattedValue: "95.0%" }]);
|
||||
});
|
||||
|
||||
it("does not report disk breach when all disks are below threshold", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", diskAlertThreshold: 90 });
|
||||
const check = makeCheck({ disk: [{ usage_percent: 0.5 }, { usage_percent: 0.6 }] });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "disk")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips disk check when diskAlertThreshold is undefined", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", diskAlertThreshold: undefined });
|
||||
const check = makeCheck({ disk: [{ usage_percent: 0.99 }] });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "disk")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips disk check when diskAlertThreshold is null", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", diskAlertThreshold: null as any });
|
||||
const check = makeCheck({ disk: [{ usage_percent: 0.99 }] });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "disk")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips disk check when disk is not an array", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", diskAlertThreshold: 80 });
|
||||
const check = makeCheck({ disk: "not-an-array" as any });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "disk")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips disks with undefined usage_percent", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", diskAlertThreshold: 50 });
|
||||
it("treats a disk with no usage figure as zero when finding the highest", () => {
|
||||
const check = makeCheck({ disk: [{ usage_percent: undefined }, { usage_percent: 0.3 }] });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, disk: true });
|
||||
|
||||
expect(breaches).toEqual([expect.objectContaining({ metric: "disk", currentValue: 30 })]);
|
||||
});
|
||||
|
||||
it("ignores disk when not flagged", () => {
|
||||
const check = makeCheck({ disk: [{ usage_percent: 0.95 }] });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, temp: true });
|
||||
|
||||
expect(breaches.find((b) => b.metric === "disk")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Temperature ──────────────────────────────────────────────────
|
||||
|
||||
describe("temperature threshold", () => {
|
||||
it("detects temperature breach from array of temperatures", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: 70 });
|
||||
describe("temperature", () => {
|
||||
it("formats the highest core temperature when flagged", () => {
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.5, temperature: [65, 75, 68] } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, temp: true });
|
||||
|
||||
expect(breaches).toContainEqual(
|
||||
expect.objectContaining({
|
||||
metric: "temp",
|
||||
currentValue: 75,
|
||||
threshold: 70,
|
||||
unit: "°C",
|
||||
formattedValue: "75.0°C",
|
||||
})
|
||||
);
|
||||
expect(breaches).toEqual([{ metric: "temp", currentValue: 75, threshold: 65, unit: "°C", formattedValue: "75.0°C" }]);
|
||||
});
|
||||
|
||||
it("handles single temperature value (non-array)", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: 70 });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.5, temperature: 80 as any } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches).toContainEqual(
|
||||
expect.objectContaining({
|
||||
metric: "temp",
|
||||
currentValue: 80,
|
||||
threshold: 70,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("does not report temperature breach when below threshold", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: 80 });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.5, temperature: [60, 65] } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "temp")).toBeUndefined();
|
||||
});
|
||||
|
||||
// A temperature exactly AT the threshold must not report a breach here, matching
|
||||
// StatusService.computeHardwareStatus's own strict `>` comparison (cpu/memory/disk
|
||||
// already agree with this file on `>`; temp alone used to diverge with `>=`, so an
|
||||
// incident-driving check at exactly the threshold never fired but the notification
|
||||
// content for a breach some OTHER metric triggered could still list temp as breaching).
|
||||
it("does not report temperature breach when exactly at threshold", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: 80 });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.5, temperature: [80] } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "temp")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips temperature check when tempAlertThreshold is undefined", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: undefined });
|
||||
it("ignores temperature when not flagged", () => {
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.5, temperature: [99] } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "temp")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips temperature check when tempAlertThreshold is null", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: null as any });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.5, temperature: [99] } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "temp")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips temperature check when cpu.temperature is falsy", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: 70 });
|
||||
const check = makeCheck({ cpu: { usage_percent: 0.5, temperature: null as any } });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
|
||||
expect(breaches.find((b) => b.metric === "temp")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips temperature check when cpu object is undefined", () => {
|
||||
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: 70 });
|
||||
const check = makeCheck({ cpu: undefined });
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, cpu: true });
|
||||
|
||||
expect(breaches.find((b) => b.metric === "temp")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Combined ─────────────────────────────────────────────────────
|
||||
|
||||
describe("combined thresholds", () => {
|
||||
it("detects multiple breaches simultaneously", () => {
|
||||
const monitor = makeMonitor({
|
||||
type: "hardware",
|
||||
cpuAlertThreshold: 80,
|
||||
memoryAlertThreshold: 70,
|
||||
diskAlertThreshold: 85,
|
||||
tempAlertThreshold: 65,
|
||||
});
|
||||
describe("combined", () => {
|
||||
it("reports every flagged metric in cpu, memory, disk, temp order", () => {
|
||||
const check = makeCheck({
|
||||
cpu: { usage_percent: 0.9, temperature: [70] },
|
||||
memory: { usage_percent: 0.85 },
|
||||
disk: [{ usage_percent: 0.95 }],
|
||||
});
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(monitor, check);
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { cpu: true, memory: true, disk: true, temp: true });
|
||||
|
||||
const metrics = breaches.map((b) => b.metric);
|
||||
expect(metrics).toContain("cpu");
|
||||
expect(metrics).toContain("memory");
|
||||
expect(metrics).toContain("disk");
|
||||
expect(metrics).toContain("temp");
|
||||
expect(breaches.map((b) => b.metric)).toEqual(["cpu", "memory", "disk", "temp"]);
|
||||
});
|
||||
|
||||
it("reports only the flagged subset", () => {
|
||||
const check = makeCheck({
|
||||
cpu: { usage_percent: 0.9, temperature: [70] },
|
||||
memory: { usage_percent: 0.85 },
|
||||
disk: [{ usage_percent: 0.95 }],
|
||||
});
|
||||
|
||||
const breaches = builder.extractThresholdBreaches(hardwareMonitor(), check, { ...none, memory: true, temp: true });
|
||||
|
||||
expect(breaches.map((b) => b.metric)).toEqual(["memory", "temp"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -518,24 +518,6 @@ describe("WorkerPipeline", () => {
|
||||
expect(check.egressStatus).toBe("ok");
|
||||
});
|
||||
|
||||
it("buffers the check when the egress assessment rejects", async () => {
|
||||
const check: Record<string, unknown> = { id: "check-1" };
|
||||
const { pipeline, deps } = createPipeline({
|
||||
networkService: { requestStatus: jest.fn<any>().mockResolvedValue(failingStatus) },
|
||||
checkService: { toCheck: jest.fn<any>().mockReturnValue(check) },
|
||||
egressService: { assessAfterFailure: jest.fn<any>().mockRejectedValue(new Error("settings unavailable")) },
|
||||
});
|
||||
|
||||
const result = await pipeline.produce(makeMonitor());
|
||||
|
||||
expect(deps.bufferService.addToBuffer).toHaveBeenCalledWith(check);
|
||||
expect(check).not.toHaveProperty("egressStatus");
|
||||
expect(result).toEqual({ status: failingStatus, check });
|
||||
expect(deps.logger.warn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: expect.stringContaining("settings unavailable"), service: "WorkerPipeline", method: "assessEgress" })
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves the field unset on a failing check when the egress check is disabled", async () => {
|
||||
const check: Record<string, unknown> = { id: "check-1" };
|
||||
const { pipeline, deps } = createPipeline({
|
||||
@@ -734,7 +716,6 @@ describe("WorkerPipeline", () => {
|
||||
expect(result).toMatchObject({
|
||||
monitor: statusChange.monitor,
|
||||
check,
|
||||
statusChange,
|
||||
decision: expect.objectContaining({
|
||||
shouldCreateIncident: true,
|
||||
shouldSendNotification: true,
|
||||
@@ -777,8 +758,7 @@ describe("WorkerPipeline", () => {
|
||||
|
||||
expect(deps.statusService.updateMonitorStatus).not.toHaveBeenCalled();
|
||||
expect(result.monitor).toBe(monitor);
|
||||
expect(result.statusChange).toMatchObject({ monitor, statusChanged: false, prevStatus: "up", code: 500 });
|
||||
expect(result.statusChange.timestamp).toEqual(expect.any(Number));
|
||||
expect(result).not.toHaveProperty("statusChange");
|
||||
expect(result.decision).toEqual({
|
||||
shouldCreateIncident: false,
|
||||
shouldResolveIncident: false,
|
||||
@@ -865,5 +845,49 @@ describe("WorkerPipeline", () => {
|
||||
const decision = await decide(makeStatusChange({ status: "paused", statusChanged: true, prevStatus: "up" }));
|
||||
expect(decision).toMatchObject({ shouldCreateIncident: false, shouldResolveIncident: false, shouldSendNotification: false });
|
||||
});
|
||||
|
||||
it("carries the threshold breaches onto a breached decision", async () => {
|
||||
const thresholdBreaches = { cpu: true, memory: false, disk: true, temp: false };
|
||||
const decision = await decide(makeStatusChange({ status: "breached", statusChanged: true, prevStatus: "up", thresholdBreaches }));
|
||||
expect(decision.thresholdBreaches).toEqual(thresholdBreaches);
|
||||
});
|
||||
|
||||
it("leaves threshold breaches off a down decision even when the status change carries them", async () => {
|
||||
const thresholdBreaches = { cpu: true, memory: false, disk: false, temp: false };
|
||||
const decision = await decide(makeStatusChange({ status: "down", statusChanged: true, prevStatus: "up", code: 500, thresholdBreaches }));
|
||||
expect(decision).not.toHaveProperty("thresholdBreaches");
|
||||
});
|
||||
|
||||
it("leaves threshold breaches off a no-op decision", async () => {
|
||||
const thresholdBreaches = { cpu: true, memory: false, disk: false, temp: false };
|
||||
const decision = await decide(makeStatusChange({ status: "breached", statusChanged: false, prevStatus: "breached", thresholdBreaches }));
|
||||
expect(decision).not.toHaveProperty("thresholdBreaches");
|
||||
});
|
||||
|
||||
// Each decision starts from a shared template. A transition must not leak its flags into the next decision
|
||||
// the same pipeline makes, whether that is a no-op evaluation or a degraded-egress short-circuit.
|
||||
it("starts every decision clean after a transition on the same pipeline", async () => {
|
||||
const updateMonitorStatus = jest
|
||||
.fn<any>()
|
||||
.mockResolvedValueOnce(makeStatusChange({ status: "down", statusChanged: true, prevStatus: "up", code: 500 }))
|
||||
.mockResolvedValueOnce(makeStatusChange({ status: "down", statusChanged: false, prevStatus: "down", code: 500 }));
|
||||
const { pipeline } = createPipeline({ statusService: { updateMonitorStatus } });
|
||||
const failing = makeCheck({ status: false, statusCode: 500, message: "Error" });
|
||||
|
||||
const first = await pipeline.evaluateCheck(failing, makeMonitor());
|
||||
const second = await pipeline.evaluateCheck(failing, makeMonitor({ status: "down" }));
|
||||
const degraded = await pipeline.evaluateCheck(makeCheck({ ...failing, egressStatus: "degraded" }), makeMonitor({ status: "down" }));
|
||||
|
||||
expect(first.decision.shouldCreateIncident).toBe(true);
|
||||
const clean = {
|
||||
shouldCreateIncident: false,
|
||||
shouldResolveIncident: false,
|
||||
shouldSendNotification: false,
|
||||
incidentReason: null,
|
||||
notificationReason: null,
|
||||
};
|
||||
expect(second.decision).toEqual(clean);
|
||||
expect(degraded.decision).toEqual(clean);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user