Files
ServerKit/backend/tests/test_serverkit_installer.py
Juan Denis ab27c7b058 fix(distro): patch all 6 failing distro-compatibility probes
- Harness: bootstrap find/awk in distro-compatibility.py (AL2023,
  Tumbleweed, SLES BCI minimal images lack them; the source-level
  suites legitimately use both)
- Python gate 3.11-3.14: Arch/Manjaro rolling ship only python 3.14
  with no versioned fallback packages (same trap as Debian 13/#99);
  align scripts/update.sh, which had drifted to a 3.11-3.12 gate
- Gentoo: add emerge as a supported installer package-manager path
  (install.sh, scripts/lib/pkg.sh, distro.py family, deps manifest);
  provision via slotted dev-lang/python:3.13 -> 3.12
- Tests: move out-of-range stubs above the new ceiling, shadow all
  locate_python candidate names (real python3.14/3.13 in Manjaro/
  Gentoo images were winning over fixtures), cover the 7th manager
- Catalog: Gentoo is no longer a documented expected failure

Verified: test_lib 32/32, test_install 124/124, test_update 76/76,
pytest test_serverkit_installer 46/46, and all 6 previously failing
images now pass both quick and provision container probes (12/12).
2026-08-28 23:31:36 -04:00

233 lines
8.7 KiB
Python

"""Unit tests for the Phase 5 installer bootstrap (serverkit_installer).
These are pure-logic tests: distro/family mapping, manifest parsing, the
firewall/service command builders, environment predicates, and the read-only
plan. No host mutation, no Flask — the package is import-safe and stdlib-only,
so this file never touches the ``app`` fixture.
"""
import textwrap
import pytest
from serverkit_installer import deps, distro, firewall, service
from serverkit_installer.main import build_plan
# ---------------------------------------------------------------------------
# distro.family_from — must agree with install.sh's os_family_from
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("distro_id,expected", [
("ubuntu", "debian"),
("debian", "debian"),
("linuxmint", "debian"),
("fedora", "fedora"),
("nobara", "fedora"),
("rocky", "rhel"),
("almalinux", "rhel"),
("centos", "rhel"),
("opensuse-leap", "suse"),
("opensuse-tumbleweed", "suse"),
("sles", "suse"),
("arch", "arch"),
("manjaro", "arch"),
("alpine", "alpine"),
("totally-unknown", "unknown"),
])
def test_family_from_known_ids(distro_id, expected):
assert distro.family_from(distro_id) == expected
@pytest.mark.parametrize("id_like,expected", [
("debian", "debian"),
("ubuntu debian", "debian"),
("rhel centos fedora", "rhel"), # RHEL clone: rhel wins over fedora
("fedora", "fedora"), # pure fedora spin
("suse opensuse", "suse"),
("arch", "arch"),
("alpine", "alpine"),
("plan9", "unknown"),
])
def test_family_from_id_like_fallback(id_like, expected):
# Unknown ID forces the ID_LIKE path.
assert distro.family_from("mystery-distro", id_like) == expected
def test_parse_os_release_and_detect(tmp_path):
osr = tmp_path / "os-release"
osr.write_text(textwrap.dedent('''\
NAME="Rocky Linux"
ID="rocky"
ID_LIKE="rhel centos fedora"
PRETTY_NAME="Rocky Linux 9.3 (Blue Onyx)"
VERSION_ID="9.3"
'''))
box = distro.detect(str(osr))
assert box.id == "rocky"
assert box.family == "rhel"
assert box.pretty_name.startswith("Rocky Linux 9")
assert box.supported is True
def test_detect_missing_os_release_is_unknown(tmp_path):
box = distro.detect(str(tmp_path / "does-not-exist"))
assert box.family == "unknown"
assert box.supported is False
# ---------------------------------------------------------------------------
# distro environment predicates
# ---------------------------------------------------------------------------
def test_is_container_via_cgroup_fixture(tmp_path):
cg = tmp_path / "cgroup"
cg.write_text("0::/system.slice/docker-abc.scope\n")
assert distro.is_container(
dockerenv=str(tmp_path / "none"),
containerenv=str(tmp_path / "none"),
container_env="",
cgroup_file=str(cg),
) is True
def test_is_container_force_and_plain_host(tmp_path):
assert distro.is_container(force=True) is True
assert distro.is_container(force=False) is False
cg = tmp_path / "cgroup"
cg.write_text("0::/init.scope\n")
assert distro.is_container(
dockerenv=str(tmp_path / "none"),
containerenv=str(tmp_path / "none"),
container_env="",
cgroup_file=str(cg),
) is False
def test_is_wsl(tmp_path):
osr = tmp_path / "osrelease"
osr.write_text("5.15.0-microsoft-standard-WSL2\n")
assert distro.is_wsl(osrelease_file=str(osr)) is True
bare = tmp_path / "bare"
bare.write_text("6.1.0-generic\n")
assert distro.is_wsl(osrelease_file=str(bare)) is False
assert distro.is_wsl(force=True) is True
def test_has_systemd_force():
assert distro.has_systemd(force=True) is True
assert distro.has_systemd(force=False) is False
# ---------------------------------------------------------------------------
# deps — manifest loader (uses the real scripts/deps/manifest.yaml)
# ---------------------------------------------------------------------------
def test_manifest_loads_and_resolves_every_family():
manifest = deps.load_manifest()
for family in distro.FAMILIES:
base = deps.base_packages(manifest, family)
# Gentoo names packages as category/atom (www-servers/nginx).
assert any(p == "nginx" or p.endswith("/nginx") for p in base), \
f"{family} base packages should include nginx"
assert deps.package_manager(manifest, family), f"{family} needs a package manager"
assert deps.python_spec(manifest, family), f"{family} needs a python spec"
def test_manifest_python_packages_match_policy():
manifest = deps.load_manifest()
# Debian 12 ships 3.11; Ubuntu/Fedora ship 3.12.
assert deps.python_spec(manifest, "debian")["package"] == "python3.11"
assert deps.python_spec(manifest, "fedora")["package"] == "python3.12"
assert deps.package_manager(manifest, "suse") == "zypper"
# ---------------------------------------------------------------------------
# firewall command builders (pure)
# ---------------------------------------------------------------------------
def test_firewall_detect_override():
assert firewall.detect("ufw") == "ufw"
def test_firewall_open_firewalld():
cmds = firewall.open_commands("firewalld", ["80/tcp", "443/tcp"])
assert ["firewall-cmd", "--permanent", "--add-port=80/tcp"] in cmds
assert ["firewall-cmd", "--permanent", "--add-port=443/tcp"] in cmds
assert cmds[-1] == ["firewall-cmd", "--reload"]
def test_firewall_open_ufw_and_iptables():
assert firewall.open_commands("ufw", ["80/tcp"]) == [["ufw", "allow", "80/tcp"]]
assert firewall.open_commands("iptables", ["443/tcp"]) == [
["iptables", "-I", "INPUT", "-p", "tcp", "--dport", "443", "-j", "ACCEPT"]
]
def test_firewall_close_mirrors_open():
assert firewall.close_commands("ufw", ["80/tcp"]) == [["ufw", "delete", "allow", "80/tcp"]]
fd = firewall.close_commands("firewalld", ["80/tcp"])
assert ["firewall-cmd", "--permanent", "--remove-port=80/tcp"] in fd
assert fd[-1] == ["firewall-cmd", "--reload"]
def test_firewall_none_is_empty():
assert firewall.open_commands("none", ["80/tcp"]) == []
def test_firewall_apply_dry_run_renders_without_executing():
cmds = firewall.open_commands("firewalld", ["80/tcp"])
rendered = firewall.apply(cmds, dry_run=True)
assert any("firewall-cmd --permanent --add-port=80/tcp" in r for r in rendered)
# ---------------------------------------------------------------------------
# service / init command builders (pure)
# ---------------------------------------------------------------------------
def test_service_detect_override():
assert service.detect("openrc") == "openrc"
@pytest.mark.parametrize("init,expected", [
("systemd", ["systemctl", "start", "serverkit"]),
("openrc", ["rc-service", "serverkit", "start"]),
("runit", ["sv", "up", "serverkit"]),
("sysvinit", ["service", "serverkit", "start"]),
])
def test_service_start_command(init, expected):
assert service.start_command(init, "serverkit") == expected
def test_service_enable_command():
assert service.enable_command("systemd", "serverkit") == ["systemctl", "enable", "serverkit"]
assert service.enable_command("openrc", "serverkit") == ["rc-update", "add", "serverkit"]
# runit/none have no simple enable command.
assert service.enable_command("none", "serverkit") is None
def test_service_reload_command():
assert service.reload_command("systemd") == ["systemctl", "daemon-reload"]
assert service.reload_command("systemd", "serverkit") == ["systemctl", "reload", "serverkit"]
def test_service_apply_dry_run():
rendered = service.apply(service.start_command("systemd", "serverkit"), dry_run=True)
assert "systemctl start serverkit" in rendered
assert service.apply(None, dry_run=True) is None
# ---------------------------------------------------------------------------
# main.build_plan — read-only end-to-end composition
# ---------------------------------------------------------------------------
def test_build_plan_for_a_forced_rhel_box(tmp_path):
osr = tmp_path / "os-release"
osr.write_text('ID="rocky"\nID_LIKE="rhel fedora"\nPRETTY_NAME="Rocky Linux 9"\n')
plan = build_plan(
os_release_path=str(osr),
firewall_backend="firewalld",
init_system="systemd",
)
assert plan["distro"]["family"] == "rhel"
assert plan["distro"]["supported"] is True
assert "nginx" in plan["packages"]["base"]
assert plan["packages"]["package_manager"] == "dnf"
assert plan["firewall"]["backend"] == "firewalld"
assert any("--add-port=80/tcp" in c for c in plan["firewall"]["open"])
assert plan["service"]["init"] == "systemd"
assert plan["service"]["start"] == "systemctl start serverkit"