Files
PacketStation/tests/server/test_doctor.py
T

109 lines
4.2 KiB
Python
Raw Normal View History

from __future__ import annotations
import json
from pathlib import Path
import pytest
from pshelpers import fake_visual, make_settings, needs_pg
from packetstation import cli, doctor, guard
from packetstation.settings import Settings
ALL = ",".join(doctor.SKIPPABLE)
def test_all_skipped_json_shape(dev_visual: None, capsys: pytest.CaptureFixture[str]) -> None:
rc = cli.main(["doctor", "--json", "--skip", ALL])
out = json.loads(capsys.readouterr().out)
assert rc == 0
assert isinstance(out, list) and out
for item in out:
assert set(item) == {"name", "status", "detail", "hint"}
assert item["status"] in ("PASS", "WARN", "FAIL", "SKIP")
by = {c["name"]: c for c in out}
assert by["config"]["status"] == "PASS"
assert by["config: stack guard"]["status"] == "PASS"
assert by["tz"]["status"] == "PASS"
for key in doctor.SKIPPABLE:
assert by[key]["status"] == "SKIP"
def test_text_report(dev_visual: None, capsys: pytest.CaptureFixture[str]) -> None:
assert cli.main(["doctor", "--skip", ALL]) == 0
assert "[PASS] config" in capsys.readouterr().out
def test_guard_failure_fails_doctor(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
monkeypatch.setattr(guard, "load_visual_settings", lambda: fake_visual(True, explicit=True))
assert cli.main(["doctor", "--json", "--skip", ALL]) == 1
by = {c["name"]: c for c in json.loads(capsys.readouterr().out)}
assert by["config: stack guard"]["status"] == "FAIL"
def test_visual_refuses_production_off_prod(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
monkeypatch.setattr(guard, "load_visual_settings", lambda: fake_visual(True, explicit=True))
[c] = doctor.check_visual(make_settings(tmp_path, stack="test"))
assert c.status == "FAIL" and "refusing PRODUCTION" in c.detail
def test_missing_stack_fails_config(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
monkeypatch.delenv("STACK")
assert cli.main(["doctor", "--json", "--skip", ALL]) == 1
by = {c["name"]: c for c in json.loads(capsys.readouterr().out)}
assert by["config"]["status"] == "FAIL" and "STACK" in by["config"]["detail"]
def test_ldap_insecure_is_a_warning(
dev_visual: None, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setenv("LDAP_ALLOW_INSECURE", "true")
assert cli.main(["doctor", "--json", "--skip", ALL]) == 0
out = json.loads(capsys.readouterr().out)
assert any(c["status"] == "WARN" and "LDAP_ALLOW_INSECURE" in c["detail"] for c in out)
def test_unknown_skip_value() -> None:
with pytest.raises(SystemExit):
doctor.parse_skip("visual,bogus")
def test_volume_checks(tmp_path: Path) -> None:
(tmp_path / "assets").mkdir()
checks = doctor.check_volume(make_settings(tmp_path))
assert checks[0].status in ("PASS", "WARN") and checks[1].status == "SKIP"
(tmp_path / "bk").mkdir()
checks = doctor.check_volume(make_settings(tmp_path, backup_dir=tmp_path / "bk"))
assert checks[1].status == "PASS"
checks = doctor.check_volume(make_settings(tmp_path, assets_dir=tmp_path / "missing"))
assert checks[0].status == "FAIL"
def test_tz() -> None:
assert doctor.check_tz("America/Denver").status == "PASS"
assert doctor.check_tz("Mars/Olympus").status == "FAIL"
def test_ldap_plain_url_refused(tmp_path: Path) -> None:
(tmp_path / "ldap").write_text("x\n")
s = make_settings(
tmp_path,
ldap_url="ldap://dc1.example.test",
ldap_bind_dn="CN=svc,DC=example,DC=test",
ldap_password_file=tmp_path / "ldap",
)
[c] = doctor.check_ldap(s)
assert c.status == "FAIL" and "without STARTTLS" in c.detail
def test_parse_report() -> None:
text = "[PASS] Settings dev / x\n[FAIL] Connect timeout\n -> check the VPN\n\n1 failed, 0 warnings"
checks = doctor.parse_report("visual", text)
assert [(c.name, c.status) for c in checks] == [("visual: Settings", "PASS"), ("visual: Connect", "FAIL")]
assert checks[1].hint == "check the VPN"
@needs_pg
def test_pg_doctor_db_check(pg_settings: Settings) -> None:
checks = doctor.check_db(pg_settings)
assert [c.status for c in checks] == ["PASS"] * 4, [(c.name, c.detail) for c in checks]