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]