from __future__ import annotations import importlib.util from pathlib import Path import sys import unittest ROOT = Path(__file__).resolve().parents[1] SCRIPT = ROOT / "scripts" / "release-doctor.py" def load_doctor_module(): spec = importlib.util.spec_from_file_location("release_doctor", SCRIPT) if spec is None or spec.loader is None: raise RuntimeError(f"Could not load {SCRIPT}") module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) return module class ReleaseDoctorTests(unittest.TestCase): def test_release_repo_names_include_catalog_and_migration_baseline_owners(self) -> None: doctor = load_doctor_module() names = set(doctor.release_repo_names(ROOT.parent)) self.assertIn("govoplan-core", names) self.assertIn("govoplan-files", names) self.assertIn("govoplan-idm", names) def test_repo_findings_detect_dirty_repositories(self) -> None: doctor = load_doctor_module() repo = doctor.RepoState( name="govoplan-files", path="/tmp/govoplan-files", exists=True, dirty_entries=(" M pyproject.toml",), pyproject_version="0.1.7", local_tag_exists=True, ) findings = doctor.repo_findings((repo,), target_version="0.1.7", target_tag="v0.1.7") self.assertEqual("blocker", findings[0].severity) self.assertEqual("repo-dirty", findings[0].check_id) self.assertIn("git status --short", [item.command for item in findings[0].commands]) def test_dubious_ownership_errors_suggest_safe_directory_command(self) -> None: doctor = load_doctor_module() repo = doctor.RepoState( name="govoplan-files", path="/tmp/govoplan-files", exists=True, safe_directory_required=True, safe_directory_command="git config --global --add safe.directory /tmp/govoplan-files", errors=("fatal: detected dubious ownership in repository",), ) findings = doctor.repo_findings((repo,), target_version="0.1.7", target_tag="v0.1.7") self.assertEqual("repo-safe-directory", findings[0].check_id) self.assertEqual("blocker", findings[0].severity) self.assertTrue(findings[0].commands[0].mutating) self.assertIn("safe.directory", findings[0].commands[0].command) def test_dubious_ownership_detection_matches_git_error(self) -> None: doctor = load_doctor_module() self.assertTrue( doctor.is_dubious_ownership_error( "fatal: detected dubious ownership in repository at '/workspace/repo'\n" "git config --global --add safe.directory /workspace/repo" ) ) def test_release_migration_strict_errors_suggest_recording_baseline(self) -> None: doctor = load_doctor_module() findings = doctor.migration_findings( { "release": { "ok": True, "graph_errors": [], "strict_errors": ["current migration heads are not recorded"], }, "dev": {"ok": True, "graph_errors": [], "strict_errors": []}, }, target_version="0.2.0", ) self.assertEqual("migration-release-baseline", findings[0].check_id) self.assertEqual("blocker", findings[0].severity) commands = [item.command for item in findings[0].commands] self.assertTrue(any("--record-release 0.2.0" in item for item in commands)) def test_catalog_findings_detect_missing_signatures(self) -> None: doctor = load_doctor_module() findings = doctor.catalog_findings( { "catalog_exists": True, "catalog_path": "/tmp/stable.json", "core_release_version": "0.1.7", "signature_count": 0, "keyring_exists": True, "key_count": 1, }, target_version="0.1.7", online=False, ) self.assertEqual("catalog-signatures", findings[0].check_id) self.assertEqual("blocker", findings[0].severity) def test_suggested_commands_are_deduplicated(self) -> None: doctor = load_doctor_module() suggested = doctor.SuggestedCommand(title="Status", command="git status --short", cwd="/tmp/repo") report = doctor.DoctorReport( generated_at="2026-07-11T00:00:00Z", workspace_root="/tmp", target_version="0.1.7", target_tag="v0.1.7", online=False, overall_status="blocked", repos=(), findings=( doctor.Finding("a", "blocker", "A", "", (suggested,)), doctor.Finding("b", "warning", "B", "", (suggested,)), ), ) self.assertEqual((suggested,), doctor.collect_suggested_commands(report)) def test_default_report_is_concise_and_keeps_details_out(self) -> None: doctor = load_doctor_module() repo = doctor.RepoState( name="govoplan-files", path="/tmp/govoplan-files", exists=True, dirty_entries=(" M pyproject.toml",), ) report = doctor.DoctorReport( generated_at="2026-07-11T00:00:00Z", workspace_root="/tmp", target_version="0.1.7", target_tag="v0.1.7", online=False, overall_status="blocked", repos=(repo,), findings=(doctor.Finding("repo-dirty", "blocker", "govoplan-files has uncommitted changes", "M pyproject.toml"),), ) concise = doctor.render_text_report(report, detailed=False, include_commands=False) detailed = doctor.render_text_report(report, detailed=True, include_commands=False) self.assertIn("dirty=1", concise) self.assertNotIn("M pyproject.toml", concise) self.assertIn("M pyproject.toml", detailed) def test_interactive_actions_offer_dirty_bulk_commit_push(self) -> None: doctor = load_doctor_module() repo = doctor.RepoState( name="govoplan-files", path="/tmp/govoplan-files", exists=True, dirty_entries=(" M pyproject.toml",), ) report = doctor.DoctorReport( generated_at="2026-07-11T00:00:00Z", workspace_root="/tmp", target_version="0.1.7", target_tag="v0.1.7", online=False, overall_status="blocked", repos=(repo,), findings=(), ) actions = dict(doctor.interactive_actions(report)) self.assertIn("commit-push-dirty", actions) if __name__ == "__main__": unittest.main()