"""Hermetic tests for repository-root MCP operator menu (#478).""" import os import stat import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent SCRIPT = REPO_ROOT / "mcp-menu.sh" DOCS = REPO_ROOT / "docs" / "mcp-menu.md" REQUIRED_MENU_LABELS = ( "Project status / root checkout health", "Author workflow prompts", "Reviewer workflow prompts", "Merger workflow prompts", "Reconciler workflow prompts", "Onboarding new project to this MCP workflow", "Proxmox deployment menu placeholder", "Create Proxmox LXC placeholder", "Run tests", "Exit", ) DANGEROUS_PATTERNS = ( "git push --force", "git push -f", "--force-with-lease", "delete-branch", "gitea_delete_branch", "issue-locks", "issue_lock_store", "curl ", "wget ", ) class TestMcpMenuScript(unittest.TestCase): def setUp(self): self.assertTrue(SCRIPT.is_file(), "mcp-menu.sh must exist at repo root") self.content = SCRIPT.read_text(encoding="utf-8") def test_script_exists_at_repo_root(self): self.assertEqual(SCRIPT.name, "mcp-menu.sh") self.assertEqual(SCRIPT.parent, REPO_ROOT) def test_executable_bit_is_set(self): mode = SCRIPT.stat().st_mode self.assertTrue(mode & stat.S_IXUSR, "mcp-menu.sh must be executable by owner") def test_shebang(self): first_line = self.content.splitlines()[0] self.assertEqual(first_line, "#!/usr/bin/env bash") def test_uses_set_euo_pipefail(self): self.assertIn("set -euo pipefail", self.content) def test_no_dangerous_commands(self): lowered = self.content.lower() for pattern in DANGEROUS_PATTERNS: with self.subTest(pattern=pattern): self.assertNotIn(pattern.lower(), lowered) def test_no_branch_deletion_verbs(self): for token in ("git branch -D", "git branch -d", "push :refs"): with self.subTest(token=token): self.assertNotIn(token, self.content) def test_contains_required_menu_labels(self): for label in REQUIRED_MENU_LABELS: with self.subTest(label=label): self.assertIn(label, self.content) def test_run_tests_prefers_run_tests_sh(self): self.assertIn('run-tests.sh', self.content) run_tests_idx = self.content.index("run_tests()") run_tests_body = self.content[run_tests_idx : run_tests_idx + 800] pytest_idx = run_tests_body.find("pytest") run_tests_sh_idx = run_tests_body.find("run-tests.sh") self.assertGreater(pytest_idx, 0) self.assertGreater(run_tests_sh_idx, 0) self.assertLess(run_tests_sh_idx, pytest_idx) def test_run_tests_fail_closed_without_runner(self): self.assertIn("fail closed", self.content.lower()) self.assertIn("exit 1", self.content) def test_proxmox_entries_are_placeholders(self): self.assertIn("TODO / issue-backed", self.content) self.assertIn("NOT implemented", self.content) def test_root_health_shows_required_fields(self): health_fn = self._extract_function("show_root_checkout_health") for snippet in ( "status --short --branch", "rev-parse HEAD", "prgs/master", "WARNING: root checkout", ): with self.subTest(snippet=snippet): self.assertIn(snippet, health_fn) def test_docs_mention_mcp_menu_sh(self): self.assertTrue(DOCS.is_file(), "docs/mcp-menu.md must exist") docs_text = DOCS.read_text(encoding="utf-8") self.assertIn("./mcp-menu.sh", docs_text) self.assertIn("placeholder", docs_text.lower()) def test_reviewer_skip_stale_request_changes_prompt_discoverable(self): # #482: the skip-already-reviewed-stale-REQUEST_CHANGES reviewer prompt # must be reachable from the reviewer menu and documented. label = "Skip already-reviewed stale REQUEST_CHANGES PR and hand off to author" reviewer_fn = self._extract_function("show_reviewer_prompts") self.assertIn(label, reviewer_fn, "new reviewer prompt label must appear in the reviewer menu") # The prompt must instruct: no duplicate terminal review mutation for a # non-stale REQUEST_CHANGES head, and must carry the author handoff. self.assertIn("do NOT submit another terminal review mutation", reviewer_fn) self.assertIn("author-ready fix prompt", reviewer_fn) docs_text = DOCS.read_text(encoding="utf-8") self.assertIn("REQUEST_CHANGES", docs_text) def _extract_function(self, name: str) -> str: marker = f"{name}() {{" start = self.content.index(marker) depth = 0 for idx in range(start, len(self.content)): char = self.content[idx] if char == "{": depth += 1 elif char == "}": depth -= 1 if depth == 0: return self.content[start : idx + 1] self.fail(f"Could not parse function {name}") if __name__ == "__main__": unittest.main()