Files
2026-09-23 11:17:01 -07:00

105 lines
4.2 KiB
TypeScript

import { describe, expect, it } from "@jest/globals";
import { describeContainerBreach, findContainerBreaches, isContainerStopped, isContainerUnhealthy } from "../../../src/domain/docker/docker-alert.ts";
import type { DockerContainerInfo } from "../../../src/domain/docker/docker.type.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
const makeContainer = (overrides?: Partial<DockerContainerInfo>): DockerContainerInfo => ({
id: "abc123",
name: "web",
image: "nginx:latest",
state: "running",
status: "Up 3 hours",
health: "none",
...overrides,
});
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
({
id: "mon-1",
type: "docker",
dockerAlertOnStopped: false,
dockerAlertOnUnhealthy: false,
...overrides,
}) as Monitor;
describe("isContainerStopped", () => {
it("treats dead as stopped", () => {
expect(isContainerStopped(makeContainer({ state: "dead" }))).toBe(true);
});
it.each([1, 137, 143])("treats exited with code %s as stopped", (exitCode) => {
expect(isContainerStopped(makeContainer({ state: "exited", exitCode }))).toBe(true);
});
it("does not treat exited with code 0 as stopped", () => {
expect(isContainerStopped(makeContainer({ state: "exited", exitCode: 0 }))).toBe(false);
});
it("treats exited with an unknown exit code as stopped", () => {
expect(isContainerStopped(makeContainer({ state: "exited" }))).toBe(true);
});
it.each(["running", "restarting", "paused", "created", "removing"] as const)("does not treat %s as stopped", (state) => {
expect(isContainerStopped(makeContainer({ state, exitCode: 1 }))).toBe(false);
});
});
describe("isContainerUnhealthy", () => {
it("treats unhealthy as unhealthy", () => {
expect(isContainerUnhealthy(makeContainer({ health: "unhealthy" }))).toBe(true);
});
it.each(["healthy", "starting", "none"] as const)("does not treat %s as unhealthy", (health) => {
expect(isContainerUnhealthy(makeContainer({ health }))).toBe(false);
});
});
describe("findContainerBreaches", () => {
const stopped = makeContainer({ name: "db", state: "exited", exitCode: 1 });
const unhealthy = makeContainer({ name: "api", health: "unhealthy" });
it("returns nothing when both switches are off", () => {
expect(findContainerBreaches(makeMonitor(), [stopped, unhealthy])).toEqual([]);
});
it("reports only stopped containers when only the stopped switch is on", () => {
const breaches = findContainerBreaches(makeMonitor({ dockerAlertOnStopped: true }), [stopped, unhealthy]);
expect(breaches).toEqual([{ name: "db", reason: "stopped", exitCode: 1 }]);
});
it("reports only unhealthy containers when only the unhealthy switch is on", () => {
const breaches = findContainerBreaches(makeMonitor({ dockerAlertOnUnhealthy: true }), [stopped, unhealthy]);
expect(breaches).toEqual([{ name: "api", reason: "unhealthy" }]);
});
it("reports both conditions when both switches are on", () => {
const breaches = findContainerBreaches(makeMonitor({ dockerAlertOnStopped: true, dockerAlertOnUnhealthy: true }), [stopped, unhealthy]);
expect(breaches.map((b) => b.reason)).toEqual(["stopped", "unhealthy"]);
});
it("reports a container that is both stopped and unhealthy once, as stopped", () => {
const both = makeContainer({ name: "worker", state: "dead", health: "unhealthy" });
const breaches = findContainerBreaches(makeMonitor({ dockerAlertOnStopped: true, dockerAlertOnUnhealthy: true }), [both]);
expect(breaches).toEqual([{ name: "worker", reason: "stopped", exitCode: undefined }]);
});
it("ignores healthy running containers", () => {
const breaches = findContainerBreaches(makeMonitor({ dockerAlertOnStopped: true, dockerAlertOnUnhealthy: true }), [makeContainer()]);
expect(breaches).toEqual([]);
});
});
describe("describeContainerBreach", () => {
it("describes an unhealthy container", () => {
expect(describeContainerBreach({ name: "api", reason: "unhealthy" })).toBe("api: unhealthy");
});
it("describes a stopped container with its exit code", () => {
expect(describeContainerBreach({ name: "db", reason: "stopped", exitCode: 137 })).toBe("db: stopped (exit code 137)");
});
it("describes a stopped container without an exit code", () => {
expect(describeContainerBreach({ name: "db", reason: "stopped" })).toBe("db: stopped");
});
});