Ctrl K
ComunidadGuías…

tests/server-checks.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: GPL-3.0-or-later
import { test } from "node:test";
import assert from "node:assert/strict";
import { raid } from "../src/checks/raid.js";
import { containers } from "../src/checks/containers.js";
import { services } from "../src/checks/services.js";
import { detectDistro } from "../src/distro.js";
import { loadThresholds } from "../src/thresholds.js";

/** Minimal ctx stub: maps command string → stdout, like tests/checks.test.js. */
function stubCtx(map, osRelease = { id: "fedora", id_like: "fedora" }) {
  return {
    osRelease,
    dist: detectDistro(osRelease),
    thresholds: loadThresholds({}),
    run: async (cmd) => {
      const entry = map[cmd] ?? map[cmd.replaceAll("'", "")];
      if (entry === undefined) return { ok: false, code: 1, stdout: "", stderr: "" };
      return { ok: true, code: 0, stdout: entry, stderr: "" };
    },
  };
}

// ---------------------------------------------------------------- raid ----------------------------------------------------------------

test("raid: a degraded mdadm array is high severity", async () => {
  const ctx = stubCtx({
    "cat /proc/mdstat 2>/dev/null":
      "Personalities : [raid1]\nmd0 : active raid1 sda1[0] sdb1[1]\n      1048576 blocks super 1.2 [2/1] [U_]\n",
    "command -v zpool 2>/dev/null": "",
  });
  const findings = await raid.run(ctx);
  assert.equal(findings.length, 1);
  assert.equal(findings[0].severity, "high");
  assert.equal(findings[0].code, "raid/degraded");
  assert.match(findings[0].title, /degraded/);
});

test("raid: a degraded ZFS pool is high severity", async () => {
  const ctx = stubCtx({
    "cat /proc/mdstat 2>/dev/null": "",
    "command -v zpool 2>/dev/null": "/usr/sbin/zpool\n",
    "zpool list -H -o name 2>/dev/null": "tank\n",
    "zpool status tank 2>/dev/null": "  pool: tank\n  state: DEGRADED\n",
  });
  const findings = await raid.run(ctx);
  assert.equal(findings.length, 1);
  assert.equal(findings[0].severity, "high");
  assert.equal(findings[0].code, "raid/degraded");
  assert.match(findings[0].evidence, /zpool:tank/);
});

// Regression: only the word "degraded" was recognised, so a pool that is
// FAULTED, UNAVAIL, REMOVED, OFFLINE or SUSPENDED fell through to
// "RAID arrays are healthy" — the worst thing this check can say.
for (const state of ["FAULTED", "UNAVAIL", "REMOVED", "SUSPENDED"]) {
  test(`raid: a ZFS pool in ${state} state is high, not healthy`, async () => {
    const ctx = stubCtx({
      "cat /proc/mdstat 2>/dev/null": "",
      "command -v zpool 2>/dev/null": "/usr/sbin/zpool\n",
      "zpool list -H -o name 2>/dev/null": "tank\n",
      "zpool status tank 2>/dev/null": `  pool: tank\n  state: ${state}\n`,
    });
    const findings = await raid.run(ctx);
    assert.equal(findings[0].code, "raid/degraded");
    assert.equal(findings[0].severity, "high");
    assert.match(findings[0].evidence, new RegExp(state));
  });
}

// A plain scrub is routine maintenance with redundancy intact; only a resilver
// or recovery is the case where the array has no redundancy while it runs.
test("raid: a running scrub is not reported as rebuilding", async () => {
  const ctx = stubCtx({
    "cat /proc/mdstat 2>/dev/null": "",
    "command -v zpool 2>/dev/null": "/usr/sbin/zpool\n",
    "zpool list -H -o name 2>/dev/null": "tank\n",
    "zpool status tank 2>/dev/null": "  pool: tank\n  state: ONLINE\n  scan: scrub in progress since Sun\n",
  });
  const findings = await raid.run(ctx);
  assert.ok(!findings.some((f) => f.code === "raid/rebuilding"), "a scrub must not be a rebuild");
});

test("raid: a healthy array reports ok (info)", async () => {
  const ctx = stubCtx({
    "cat /proc/mdstat 2>/dev/null":
      "md0 : active raid1 sda1[0] sdb1[1]\n      1048576 blocks super 1.2 [2/2] [UU]\n",
    "command -v zpool 2>/dev/null": "",
  });
  const findings = await raid.run(ctx);
  assert.equal(findings.length, 1);
  assert.equal(findings[0].severity, "info");
  assert.equal(findings[0].code, "raid/ok");
});

test("raid: rebuilding (resync) array is medium", async () => {
  const ctx = stubCtx({
    "cat /proc/mdstat 2>/dev/null":
      "md0 : active raid1 sda1[0] sdb1[1]\n      1048576 blocks super 1.2 [2/2] [UU]\n      [>....]  recovery = 12.3% (12345/1048576) finish=10.0min\n",
    "command -v zpool 2>/dev/null": "",
  });
  const findings = await raid.run(ctx);
  assert.equal(findings.length, 1);
  assert.equal(findings[0].severity, "medium");
  assert.equal(findings[0].code, "raid/rebuilding");
});

test("raid: no arrays and no zpool stays silent", async () => {
  const ctx = stubCtx({
    "cat /proc/mdstat 2>/dev/null": "Personalities : [linear] [raid0]\nunused devices: <none>\n",
    "command -v zpool 2>/dev/null": "",
  });
  const findings = await raid.run(ctx);
  assert.equal(findings.length, 0);
});

// ------------------------------------------------------------ containers ------------------------------------------------------------

test("containers: a podman container killed by OOM (exit 137) is high", async () => {
  const ctx = stubCtx({
    "command -v podman 2>/dev/null": "/usr/bin/podman\n",
    "systemctl is-active docker 2>/dev/null": "inactive\n",
    "podman info >/dev/null 2>&1 && echo ok || echo fail": "ok\n",
    "podman ps -a --format '{{.Names}} {{.Status}}' 2>/dev/null": "web1 Exited (137) 2 hours ago\n",
  });
  const findings = await containers.run(ctx);
  const oom = findings.find((f) => f.code === "containers/oom");
  assert.ok(oom, "expected a containers/oom finding");
  assert.equal(oom.severity, "high");
});

test("containers: a podman container that exited non-zero is medium", async () => {
  const ctx = stubCtx({
    "command -v podman 2>/dev/null": "/usr/bin/podman\n",
    "systemctl is-active docker 2>/dev/null": "inactive\n",
    "podman info >/dev/null 2>&1 && echo ok || echo fail": "ok\n",
    "podman ps -a --format '{{.Names}} {{.Status}}' 2>/dev/null": "api Exited (1) 5 minutes ago\n",
  });
  const findings = await containers.run(ctx);
  const dead = findings.find((f) => f.code === "containers/dead");
  assert.ok(dead, "expected a containers/dead finding");
  assert.equal(dead.severity, "medium");
});

test("containers: a docker container stuck restarting is medium", async () => {
  const ctx = stubCtx({
    "command -v podman 2>/dev/null": "",
    "command -v docker 2>/dev/null": "/usr/bin/docker\n",
    "systemctl is-active docker 2>/dev/null": "active\n",
    "docker ps -a --format '{{.Names}} {{.Status}}' 2>/dev/null": "db Restarting (1) 5 seconds ago\n",
  });
  const findings = await containers.run(ctx);
  const restarting = findings.find((f) => f.code === "containers/restarting");
  assert.ok(restarting, "expected a containers/restarting finding");
  assert.equal(restarting.severity, "medium");
});

test("containers: healthy running containers report ok", async () => {
  const ctx = stubCtx({
    "command -v podman 2>/dev/null": "/usr/bin/podman\n",
    "systemctl is-active docker 2>/dev/null": "inactive\n",
    "podman info >/dev/null 2>&1 && echo ok || echo fail": "ok\n",
    "podman ps -a --format '{{.Names}} {{.Status}}' 2>/dev/null": "web1 Up 3 hours\n",
  });
  const findings = await containers.run(ctx);
  const ok = findings.find((f) => f.code === "containers/ok");
  assert.ok(ok, "expected a containers/ok finding when all containers are healthy");
  assert.ok(!findings.some((f) => f.code === "containers/dead"), "a clean exit must not be flagged dead");
});

// ------------------------------------------------------------- services -------------------------------------------------------------

test("services: a unit in auto-restart loop is high severity", async () => {
  const ctx = stubCtx({
    "systemctl --failed --no-legend --plain": "",
    "systemctl --user --failed --no-legend --plain": "",
    "systemctl list-units --type=service --no-legend --plain 2>/dev/null | grep -i 'auto-restart'":
      "myapp.service loaded active auto-restart  myapp.service\n",
    "systemctl show myapp.service -p NRestarts --value 2>/dev/null": "5\n",
  });
  const findings = await services.run(ctx);
  const loop = findings.find((f) => f.code === "services/restart-loop");
  assert.ok(loop, "expected a services/restart-loop finding");
  assert.equal(loop.severity, "high");
  assert.match(loop.evidence, /myapp.service/);
});

test("services: no failed and no looping units produces no finding", async () => {
  const ctx = stubCtx({
    "systemctl --failed --no-legend --plain": "",
    "systemctl --user --failed --no-legend --plain": "",
    "systemctl list-units --type=service --no-legend --plain 2>/dev/null | grep -i 'auto-restart'": "",
  });
  const findings = await services.run(ctx);
  assert.equal(findings.length, 0);
});

/**
 * 143 is 128 + 15: the SIGTERM a plain `podman stop`, a `systemctl stop` or an
 * orderly shutdown sends. A container that stopped when it was told to stopped
 * cleanly, and this check is not a monitor — calling that "did not exit cleanly"
 * put a medium finding (8 points) on every machine with a deliberately stopped
 * container.
 */
test("containers: exit 143 is a plain stop, not a failure", async () => {
  const ctx = stubCtx({
    "command -v podman 2>/dev/null": "/usr/bin/podman\n",
    "systemctl is-active docker 2>/dev/null": "inactive\n",
    "podman info >/dev/null 2>&1 && echo ok || echo fail": "ok\n",
    "podman ps -a --format '{{.Names}} {{.Status}}' 2>/dev/null": "n8n Exited (143) 44 hours ago\n",
  });
  const findings = await containers.run(ctx);
  assert.ok(
    !findings.some((f) => f.code === "containers/dead"),
    "143 is SIGTERM — the signal `podman stop` sends"
  );
});

test("containers: exit 130 (Ctrl-C / SIGINT) is also a plain stop", async () => {
  const ctx = stubCtx({
    "command -v podman 2>/dev/null": "/usr/bin/podman\n",
    "systemctl is-active docker 2>/dev/null": "inactive\n",
    "podman info >/dev/null 2>&1 && echo ok || echo fail": "ok\n",
    "podman ps -a --format '{{.Names}} {{.Status}}' 2>/dev/null": "tmp Exited (130) 2 hours ago\n",
  });
  const findings = await containers.run(ctx);
  assert.ok(!findings.some((f) => f.code === "containers/dead"), "130 is SIGINT");
});