add tests for notificaitonMessageBuilder

This commit is contained in:
Alex Holliday
2026-04-09 09:41:17 -07:00
parent 88e0660762
commit bb55f3b3eb
@@ -0,0 +1,761 @@
import { describe, expect, it, beforeEach } from "@jest/globals";
import { NotificationMessageBuilder } from "../../../src/service/infrastructure/notificationMessageBuilder.ts";
import type { Monitor, MonitorStatusResponse, HardwareStatusPayload } from "../../../src/types/index.ts";
import type { MonitorActionDecision } from "../../../src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
({
id: "mon-1",
name: "Test Monitor",
url: "https://example.com",
type: "http",
status: "down",
teamId: "team-1",
cpuAlertThreshold: undefined,
memoryAlertThreshold: undefined,
diskAlertThreshold: undefined,
tempAlertThreshold: undefined,
...overrides,
}) as Monitor;
const makeDecision = (overrides?: Partial<MonitorActionDecision>): MonitorActionDecision => ({
shouldCreateIncident: false,
shouldResolveIncident: false,
shouldSendNotification: true,
incidentReason: null,
notificationReason: "status_change",
...overrides,
});
const makeStatusResponse = (overrides?: Partial<MonitorStatusResponse>): MonitorStatusResponse =>
({
monitorId: "mon-1",
teamId: "team-1",
type: "http",
status: false,
code: 500,
message: "Internal Server Error",
...overrides,
}) as MonitorStatusResponse;
const makeHardwarePayload = (overrides?: Partial<HardwareStatusPayload["data"]>): HardwareStatusPayload =>
({
data: {
cpu: { usage_percent: 0.5, temperature: [45] },
memory: { usage_percent: 0.6 },
disk: [{ usage_percent: 0.7 }],
...overrides,
},
}) as HardwareStatusPayload;
// ── Tests ────────────────────────────────────────────────────────────────────
describe("NotificationMessageBuilder", () => {
let builder: NotificationMessageBuilder;
beforeEach(() => {
builder = new NotificationMessageBuilder();
});
describe("SERVICE_NAME", () => {
it("returns NotificationMessageBuilder", () => {
expect(NotificationMessageBuilder.SERVICE_NAME).toBe("NotificationMessageBuilder");
});
});
// ── buildMessage ─────────────────────────────────────────────────────
describe("buildMessage", () => {
it("builds a monitor_down message", () => {
const monitor = makeMonitor({ status: "down" });
const decision = makeDecision();
const response = makeStatusResponse();
const msg = builder.buildMessage(monitor, response, decision, "https://app.example.com");
expect(msg.type).toBe("monitor_down");
expect(msg.severity).toBe("critical");
expect(msg.monitor).toEqual({
id: "mon-1",
name: "Test Monitor",
url: "https://example.com",
type: "http",
status: "down",
});
expect(msg.content.title).toBe("Monitor Down: Test Monitor");
expect(msg.clientHost).toBe("https://app.example.com");
expect(msg.metadata).toEqual({
teamId: "team-1",
notificationReason: "status_change",
});
});
it("builds a monitor_up message", () => {
const monitor = makeMonitor({ status: "up", type: "http" });
const decision = makeDecision({ notificationReason: "status_change" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), decision, "https://app.example.com");
expect(msg.type).toBe("monitor_up");
expect(msg.severity).toBe("success");
expect(msg.content.title).toBe("Monitor Recovered: Test Monitor");
});
it("builds a threshold_breach message", () => {
const monitor = makeMonitor({ status: "up", type: "hardware", cpuAlertThreshold: 80 });
const decision = makeDecision({ notificationReason: "threshold_breach" });
const response = makeStatusResponse({
type: "hardware",
payload: makeHardwarePayload({ cpu: { usage_percent: 0.9, temperature: [50] } as any }),
} as any);
const msg = builder.buildMessage(monitor, response, decision, "https://app.example.com");
expect(msg.type).toBe("threshold_breach");
expect(msg.severity).toBe("warning");
expect(msg.content.title).toBe("Threshold Exceeded: Test Monitor");
expect(msg.content.thresholds).toBeDefined();
expect(msg.content.thresholds!.length).toBeGreaterThan(0);
});
it("builds a threshold_resolved message for hardware monitor recovering", () => {
const monitor = makeMonitor({ status: "up", type: "hardware" });
const decision = makeDecision({ notificationReason: "status_change" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), decision, "https://app.example.com");
expect(msg.type).toBe("threshold_resolved");
expect(msg.severity).toBe("success");
expect(msg.content.title).toBe("Thresholds Resolved: Test Monitor");
});
it("uses notificationReason from decision, falling back to status_change", () => {
const monitor = makeMonitor({ status: "down" });
const decision = makeDecision({ notificationReason: null });
const msg = builder.buildMessage(monitor, makeStatusResponse(), decision, "https://app.example.com");
expect(msg.metadata.notificationReason).toBe("status_change");
});
});
// ── determineNotificationType (via buildMessage) ─────────────────────
describe("determineNotificationType", () => {
it("returns monitor_down when status is down, even if notificationReason is threshold_breach", () => {
const monitor = makeMonitor({ status: "down" });
const decision = makeDecision({ notificationReason: "threshold_breach" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), decision, "");
expect(msg.type).toBe("monitor_down");
});
it("returns threshold_breach when reason is threshold_breach and status is not down", () => {
const monitor = makeMonitor({ status: "up", type: "hardware" });
const decision = makeDecision({ notificationReason: "threshold_breach" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), decision, "");
expect(msg.type).toBe("threshold_breach");
});
it("returns threshold_resolved for hardware monitor with status_change and up", () => {
const monitor = makeMonitor({ status: "up", type: "hardware" });
const decision = makeDecision({ notificationReason: "status_change" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), decision, "");
expect(msg.type).toBe("threshold_resolved");
});
it("returns monitor_up for non-hardware monitor with status up", () => {
const monitor = makeMonitor({ status: "up", type: "http" });
const decision = makeDecision({ notificationReason: "status_change" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), decision, "");
expect(msg.type).toBe("monitor_up");
});
it("returns monitor_up as default for unrecognized status", () => {
const monitor = makeMonitor({ status: "unknown" as any });
const decision = makeDecision({ notificationReason: null });
const msg = builder.buildMessage(monitor, makeStatusResponse(), decision, "");
expect(msg.type).toBe("monitor_up");
});
});
// ── determineSeverity (via buildMessage) ─────────────────────────────
describe("determineSeverity", () => {
it("returns critical for monitor_down", () => {
const msg = builder.buildMessage(makeMonitor({ status: "down" }), makeStatusResponse(), makeDecision(), "");
expect(msg.severity).toBe("critical");
});
it("returns warning for threshold_breach", () => {
const msg = builder.buildMessage(
makeMonitor({ status: "up", type: "hardware" }),
makeStatusResponse(),
makeDecision({ notificationReason: "threshold_breach" }),
""
);
expect(msg.severity).toBe("warning");
});
it("returns success for monitor_up", () => {
const msg = builder.buildMessage(
makeMonitor({ status: "up", type: "http" }),
makeStatusResponse(),
makeDecision({ notificationReason: "status_change" }),
""
);
expect(msg.severity).toBe("success");
});
it("returns success for threshold_resolved", () => {
const msg = builder.buildMessage(
makeMonitor({ status: "up", type: "hardware" }),
makeStatusResponse(),
makeDecision({ notificationReason: "status_change" }),
""
);
expect(msg.severity).toBe("success");
});
});
// ── buildContent variants ────────────────────────────────────────────
describe("buildContent", () => {
describe("monitor_down", () => {
it("includes response code and error message when present", () => {
const monitor = makeMonitor({ status: "down" });
const response = makeStatusResponse({ code: 503, message: "Service Unavailable" });
const msg = builder.buildMessage(monitor, response, makeDecision(), "");
expect(msg.content.details).toContain("Response Code: 503");
expect(msg.content.details).toContain("Error: Service Unavailable");
});
it("omits response code when falsy", () => {
const monitor = makeMonitor({ status: "down" });
const response = makeStatusResponse({ code: 0, message: "" });
const msg = builder.buildMessage(monitor, response, makeDecision(), "");
expect(msg.content.details).not.toContainEqual(expect.stringContaining("Response Code:"));
expect(msg.content.details).not.toContainEqual(expect.stringContaining("Error:"));
});
it("includes URL, Status, and Type in details", () => {
const monitor = makeMonitor({ status: "down" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), makeDecision(), "");
expect(msg.content.details).toContain("URL: https://example.com");
expect(msg.content.details).toContain("Status: Down");
expect(msg.content.details).toContain("Type: http");
});
it("sets summary text", () => {
const monitor = makeMonitor({ status: "down" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), makeDecision(), "");
expect(msg.content.summary).toBe('Monitor "Test Monitor" is currently down and unreachable.');
});
it("sets timestamp", () => {
const monitor = makeMonitor({ status: "down" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), makeDecision(), "");
expect(msg.content.timestamp).toBeInstanceOf(Date);
});
});
describe("monitor_up", () => {
it("includes recovery details", () => {
const monitor = makeMonitor({ status: "up", type: "http" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), makeDecision(), "");
expect(msg.content.title).toBe("Monitor Recovered: Test Monitor");
expect(msg.content.summary).toBe('Monitor "Test Monitor" is back up and operational.');
expect(msg.content.details).toContain("Status: Up");
});
});
describe("threshold_breach", () => {
it("includes threshold breaches in content", () => {
const monitor = makeMonitor({ status: "up", type: "hardware", cpuAlertThreshold: 80 });
const response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.9, temperature: [50] } as any }),
} as any);
const msg = builder.buildMessage(monitor, response, makeDecision({ notificationReason: "threshold_breach" }), "");
expect(msg.content.thresholds).toEqual(
expect.arrayContaining([
expect.objectContaining({
metric: "cpu",
currentValue: 90,
threshold: 80,
}),
])
);
});
});
describe("threshold_resolved", () => {
it("includes resolved details", () => {
const monitor = makeMonitor({ status: "up", type: "hardware" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), makeDecision(), "");
expect(msg.content.title).toBe("Thresholds Resolved: Test Monitor");
expect(msg.content.summary).toBe('Monitor "Test Monitor" thresholds have returned to normal.');
expect(msg.content.details).toContain("Status: Up");
});
});
describe("default content", () => {
it("builds default content for unhandled notification type", () => {
// Force default by creating a scenario where type falls through
// We can test buildContent default by subclassing or testing indirectly
// The default case in buildContent handles "test" type among others
// Since determineNotificationType never returns "test", we test via
// a monitor with unrecognized status that maps to monitor_up
// Instead, let's verify the default case exists by checking that
// non-standard types would get default content
const monitor = makeMonitor({ status: "up", type: "http" });
const msg = builder.buildMessage(monitor, makeStatusResponse(), makeDecision(), "");
// monitor_up is handled, so content should be monitor_up specific
expect(msg.content.title).toBe("Monitor Recovered: Test Monitor");
});
});
});
// ── determineSeverity edge cases (via private method) ────────────────
describe("determineSeverity edge cases", () => {
it("returns info for test type", () => {
const result = (builder as any).determineSeverity("test");
expect(result).toBe("info");
});
it("returns info for unknown type (default)", () => {
const result = (builder as any).determineSeverity("unknown_type");
expect(result).toBe("info");
});
});
// ── buildContent edge cases (via private method) ─────────────────────
describe("buildContent edge cases", () => {
it("returns default content for unhandled notification type", () => {
const monitor = makeMonitor({ status: "up", type: "http" });
const result = (builder as any).buildContent("test", monitor, makeStatusResponse());
expect(result.title).toBe("Monitor: Test Monitor");
expect(result.summary).toBe('Status update for monitor "Test Monitor".');
expect(result.details).toContain("URL: https://example.com");
expect(result.details).toContain("Status: up");
expect(result.details).toContain("Type: http");
});
});
// ── extractThresholdBreaches ─────────────────────────────────────────
describe("extractThresholdBreaches", () => {
it("returns empty array for non-hardware monitor", () => {
const monitor = makeMonitor({ type: "http" });
const response = makeStatusResponse();
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches).toEqual([]);
});
it("returns empty array when payload is missing", () => {
const monitor = makeMonitor({ type: "hardware" });
const response = makeStatusResponse({ payload: undefined } as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches).toEqual([]);
});
it("returns empty array when hardware data is missing", () => {
const monitor = makeMonitor({ type: "hardware" });
const response = makeStatusResponse({ payload: { data: undefined } } as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches).toEqual([]);
});
// ── CPU ──────────────────────────────────────────────────────────
describe("cpu threshold", () => {
it("detects CPU breach when usage exceeds threshold", () => {
const monitor = makeMonitor({ type: "hardware", cpuAlertThreshold: 80 });
const response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.9, temperature: [50] } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches).toContainEqual(
expect.objectContaining({
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 response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.5, temperature: [50] } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches.find((b) => b.metric === "cpu")).toBeUndefined();
});
it("skips CPU check when cpuAlertThreshold is undefined", () => {
const monitor = makeMonitor({ type: "hardware", cpuAlertThreshold: undefined });
const response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.99 } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.99 } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: undefined } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: undefined as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 });
const response = makeStatusResponse({
payload: makeHardwarePayload({ memory: { usage_percent: 0.85 } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches).toContainEqual(
expect.objectContaining({
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 response = makeStatusResponse({
payload: makeHardwarePayload({ memory: { usage_percent: 0.5 } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches.find((b) => b.metric === "memory")).toBeUndefined();
});
it("skips memory check when memoryAlertThreshold is undefined", () => {
const monitor = makeMonitor({ type: "hardware", memoryAlertThreshold: undefined });
const response = makeStatusResponse({
payload: makeHardwarePayload({ memory: { usage_percent: 0.99 } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ memory: { usage_percent: 0.99 } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ memory: { usage_percent: undefined } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ memory: undefined as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 });
const response = makeStatusResponse({
payload: makeHardwarePayload({
disk: [{ usage_percent: 0.5 }, { usage_percent: 0.95 }, { usage_percent: 0.7 }] as any,
}),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches).toContainEqual(
expect.objectContaining({
metric: "disk",
currentValue: 95,
threshold: 80,
formattedValue: "95.0%",
})
);
});
it("does not report disk breach when all disks are below threshold", () => {
const monitor = makeMonitor({ type: "hardware", diskAlertThreshold: 90 });
const response = makeStatusResponse({
payload: makeHardwarePayload({
disk: [{ usage_percent: 0.5 }, { usage_percent: 0.6 }] as any,
}),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches.find((b) => b.metric === "disk")).toBeUndefined();
});
it("skips disk check when diskAlertThreshold is undefined", () => {
const monitor = makeMonitor({ type: "hardware", diskAlertThreshold: undefined });
const response = makeStatusResponse({
payload: makeHardwarePayload({ disk: [{ usage_percent: 0.99 }] as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ disk: [{ usage_percent: 0.99 }] as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ disk: "not-an-array" as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches.find((b) => b.metric === "disk")).toBeUndefined();
});
it("skips disks with undefined usage_percent", () => {
const monitor = makeMonitor({ type: "hardware", diskAlertThreshold: 50 });
const response = makeStatusResponse({
payload: makeHardwarePayload({
disk: [{ usage_percent: undefined }, { usage_percent: 0.3 }] as any,
}),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 });
const response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.5, temperature: [65, 75, 68] } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches).toContainEqual(
expect.objectContaining({
metric: "temp",
currentValue: 75,
threshold: 70,
unit: "°C",
formattedValue: "75.0°C",
})
);
});
it("handles single temperature value (non-array)", () => {
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: 70 });
const response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.5, temperature: 80 } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.5, temperature: [60, 65] } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
expect(breaches.find((b) => b.metric === "temp")).toBeUndefined();
});
it("skips temperature check when tempAlertThreshold is undefined", () => {
const monitor = makeMonitor({ type: "hardware", tempAlertThreshold: undefined });
const response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.5, temperature: [99] } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.5, temperature: [99] } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: { usage_percent: 0.5, temperature: null } as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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 response = makeStatusResponse({
payload: makeHardwarePayload({ cpu: undefined as any }),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
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,
});
const response = makeStatusResponse({
payload: makeHardwarePayload({
cpu: { usage_percent: 0.9, temperature: [70] } as any,
memory: { usage_percent: 0.85 } as any,
disk: [{ usage_percent: 0.95 }] as any,
}),
} as any);
const breaches = builder.extractThresholdBreaches(monitor, response);
const metrics = breaches.map((b) => b.metric);
expect(metrics).toContain("cpu");
expect(metrics).toContain("memory");
expect(metrics).toContain("disk");
expect(metrics).toContain("temp");
});
});
});
});