Files
Gitea-Tools/tests/test_mcp_menu_script.py
T
sysadmin 7f2b9f36de feat: add workflow dashboard for queue, leases, and next safe action (Closes #605)
Read-only MCP tool gitea_workflow_dashboard plus mcp-menu entry so operators
and LLMs can see PR/issue queues, leases, terminal locks, blockers, and exact
next-safe prompts without reconstructing state from comments. Never assigns
work or presents blocked/terminal-locked items as safe.
2026-07-19 14:43:10 -04:00

155 lines
6.0 KiB
Python

"""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",
"Workflow dashboard (queue, leases, next safe action)",
"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_workflow_dashboard_menu_entry_is_read_only(self):
# #605: dashboard entry documents gitea_workflow_dashboard and never
# mutates Gitea / assigns work from the shell menu.
label = "Workflow dashboard (queue, leases, next safe action)"
self.assertIn(label, self.content)
dash_fn = self._extract_function("show_workflow_dashboard_help")
self.assertIn("gitea_workflow_dashboard", dash_fn)
self.assertIn("gitea_allocate_next_work", dash_fn)
self.assertIn("Read-only", dash_fn)
self.assertIn("never presented as safe", dash_fn.lower())
for bad in ("gitea_merge_pr", "gitea_submit_pr_review", "git push"):
with self.subTest(bad=bad):
self.assertNotIn(bad, dash_fn)
docs_text = DOCS.read_text(encoding="utf-8")
self.assertIn("gitea_workflow_dashboard", docs_text)
self.assertIn("Workflow dashboard", docs_text)
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()