tests/fix-families.test.js
Rama main · réplica de lectura del repositorio original.
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* The safe-fix catalog is per-family. This is the matrix that says what each
* family may be told to run — and, just as important, where the catalog must
* stay silent.
*
* It exists because the catalog used to fall through to a default branch for
* every family it did not know, which is how openSUSE was told to run
* `dnf autoremove`, Alpine `systemctl enable firewalld`, and Void
* `localectl set-locale`. Those commands do not exist there. A fix that cannot
* be run is worse than no fix: it is a wrong instruction with the tool's
* authority behind it.
*
* Sources for every command in the matrix are in
* internal/research-distro-commands.md and
* internal/research-firewall-trim-locales-flatpak.md (primary distro docs).
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { planFixes } from "../src/fix.js";
const finding = (code) => ({ id: 1, code, severity: "medium", title: "t", detail: null, evidence: "" });
/** All commands the catalog would emit for a code on a system, joined. */
function cmds(code, system) {
const plan = planFixes([finding(code)], { system });
return plan.flatMap((p) => p.commands.map((c) => c.cmd));
}
const emit = (code, system) => cmds(code, system).join(" ; ");
const SYS = {
debian: { family: "debian", id: "ubuntu", hasSystemd: true },
fedora: { family: "fedora", id: "fedora", hasSystemd: true },
arch: { family: "arch", id: "arch", hasSystemd: true },
leap: { family: "suse", id: "opensuse-leap", hasSystemd: true },
tumbleweed: { family: "suse", id: "opensuse-tumbleweed", hasSystemd: true },
alpine: { family: "alpine", id: "alpine", hasSystemd: false },
void: { family: "void", id: "void", hasSystemd: false },
gentoo: { family: "gentoo", id: "gentoo", hasSystemd: false },
ostree: { family: "fedora", id: "bazzite", imageBased: true, hasSystemd: true },
};
// ---------------------------------------------------------------- updates ----
test("updates/pending: Leap gets an update, Tumbleweed gets the dist-upgrade", () => {
assert.equal(emit("updates/pending", SYS.leap), "sudo zypper up");
assert.equal(emit("updates/pending", SYS.tumbleweed), "sudo zypper dup");
});
test("updates/pending: no upgrade command for systems whose package manager the check never reads", () => {
assert.deepEqual(cmds("updates/pending", SYS.void), []);
assert.deepEqual(cmds("updates/pending", SYS.gentoo), []);
});
test("updates/pending: Alpine gets an apk command, image systems the atomic one", () => {
assert.match(emit("updates/pending", SYS.alpine), /^sudo apk /);
assert.equal(emit("updates/pending", SYS.ostree), "rpm-ostree upgrade");
});
// ---------------------------------------------------------------- orphans ----
test("orphans: openSUSE gets no bulk autoremove (zypper has no such command)", () => {
for (const code of ["orphans/many", "orphans/some"]) {
assert.ok(!/\bdnf\b/.test(emit(code, SYS.leap)), `${code} must not emit dnf on openSUSE`);
assert.deepEqual(cmds(code, SYS.leap), [], `${code}: openSUSE has no bulk command to offer`);
}
});
test("orphans: families with a real autoremove still get it", () => {
assert.match(emit("orphans/many", SYS.debian), /apt autoremove/);
assert.match(emit("orphans/some", SYS.debian), /apt autoremove/);
assert.match(emit("orphans/some", SYS.fedora), /dnf autoremove/);
assert.match(emit("orphans/some", SYS.arch), /pacman -Rns/);
});
// ---------------------------------------------------------------- locales ----
test("locales/broken: only systemd families are told to use localectl", () => {
assert.ok(!/localectl/.test(emit("locales/broken", SYS.void)));
assert.ok(!/localectl/.test(emit("locales/broken", SYS.gentoo)));
assert.deepEqual(cmds("locales/broken", SYS.alpine), [], "musl has no locale generation");
assert.deepEqual(cmds("locales/broken", SYS.void), [], "Void needs a config edit before any command");
});
test("locales/broken: each glibc family gets the command its distro documents", () => {
assert.match(emit("locales/broken", SYS.debian), /locale-gen/);
assert.match(emit("locales/broken", SYS.arch), /locale-gen/);
assert.match(emit("locales/broken", SYS.gentoo), /locale-gen/);
assert.match(emit("locales/broken", SYS.leap), /localectl/);
assert.match(emit("locales/broken", SYS.fedora), /localectl/);
});
// ------------------------------------------------------------------ disk ----
test("disk/full: journal vacuum only where a journal exists, plus the family cache", () => {
assert.match(emit("disk/full", SYS.alpine), /apk cache clean/);
assert.ok(!/journalctl/.test(emit("disk/full", SYS.void)), "runit has no journalctl");
assert.match(emit("disk/full", SYS.void), /xbps-remove -O/);
assert.deepEqual(cmds("disk/full", SYS.gentoo), [], "no cache command that is safe to suggest");
});
// ------------------------------------------------------------------ boot ----
test("boot/full: old-kernel cleanup follows the family, never dnf/apt elsewhere", () => {
assert.match(emit("boot/full", SYS.leap), /zypper purge-kernels/);
assert.match(emit("boot/full", SYS.void), /vkpurge rm all/);
for (const sys of [SYS.arch, SYS.alpine, SYS.gentoo]) {
assert.ok(!/\bdnf\b|\bapt\b/.test(emit("boot/full", sys)), `dnf/apt must not appear for ${sys.id}`);
}
});
// -------------------------------------------------------------- packages ----
test("packages/broken: repair is the family's own tool", () => {
assert.match(emit("packages/broken", SYS.debian), /dpkg --configure -a/);
assert.match(emit("packages/broken", SYS.leap), /zypper verify/);
assert.match(emit("packages/broken", SYS.alpine), /apk fix/);
assert.match(emit("packages/broken", SYS.void), /xbps-pkgdb -a/);
assert.deepEqual(cmds("packages/broken", SYS.gentoo), []);
});
// ----------------------------------------------------------------- fstrim ----
test("fstrim/disabled: the enable command is systemd-only, so it is withheld elsewhere", () => {
assert.match(emit("fstrim/disabled", SYS.fedora), /systemctl enable --now fstrim\.timer/);
assert.deepEqual(cmds("fstrim/disabled", SYS.gentoo), []);
assert.deepEqual(cmds("fstrim/disabled", SYS.void), []);
});
// ------------------------------------------------------- commands we cut ------
test("no fix command for advice that is not ours to execute", () => {
// Enabling a firewall: no safe one-liner exists for every family (awall can
// lock the user out), so the finding reports the state and the catalog is
// silent. Installing a backup tool is advice, not a repair.
for (const sys of Object.values(SYS)) {
assert.deepEqual(cmds("security/no-firewall", sys), [], `firewall command for ${sys.id}`);
assert.deepEqual(cmds("backup/none", sys), [], `backup-install command for ${sys.id}`);
}
});
test("wifi/blocked: unblock via rfkill, which every family has", () => {
const out = emit("wifi/blocked", SYS.alpine);
assert.match(out, /rfkill unblock wifi/);
assert.ok(!/nmcli/.test(out), "nmcli is not present on every family");
});
test("security/autologin: Debian's GDM path is named, not just Red Hat's", () => {
assert.match(emit("security/autologin", SYS.debian), /gdm3/);
});