"""Consolidated MVP coverage for the internal web UI (#436).""" from __future__ import annotations import importlib import os import sys import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from starlette.testclient import TestClient from starlette.routing import Route from webui.app import create_app _REPO_ROOT = Path(__file__).resolve().parent.parent # HTML pages and JSON exports registered on master MVP. MVP_GET_ROUTES: tuple[tuple[str, str | tuple[str, ...]], ...] = ( ("/", "Operator console"), ("/health", "mcp-control-plane-webui"), ("/queue", "Live queue"), ("/api/queue", ("prs", "issues")), ("/projects", "Gitea-Tools"), ("/api/projects", "projects"), ("/prompts", "Prompt library"), ("/api/prompts", "prompts"), ("/runtime", "Runtime"), ("/audit", "Audit"), ("/worktrees", "Worktrees"), ("/leases", "Leases"), ) MUTATION_METHODS = ("POST", "PUT", "PATCH", "DELETE") def _import_optional(module_name: str): try: return importlib.import_module(module_name) except ImportError: return None class TestWebuiServerStartup(unittest.TestCase): def test_create_app_imports_cleanly(self): app = create_app() self.assertIsNotNone(app) def test_main_module_imports(self): import webui.__main__ as main_mod self.assertTrue(callable(main_mod.main)) class TestWebuiRouteRendering(unittest.TestCase): def setUp(self): self.client = TestClient(create_app()) def test_all_mvp_get_routes_render(self): for path, needle in MVP_GET_ROUTES: with self.subTest(path=path): response = self.client.get(path) self.assertEqual(response.status_code, 200, path) if path == "/health": assert isinstance(needle, str) self.assertEqual(response.json()["service"], needle) elif path.startswith("/api/"): data = response.json() if isinstance(needle, tuple): for key in needle: self.assertIn(key, data) else: self.assertIn(needle, data) else: assert isinstance(needle, str) self.assertIn(needle, response.text) def test_registered_routes_include_mvp_paths(self): app = create_app() get_paths = { route.path for route in app.routes if isinstance(route, Route) and "GET" in route.methods } for path, _ in MVP_GET_ROUTES: with self.subTest(path=path): self.assertIn(path, get_paths) class TestWebuiReadOnlyGuards(unittest.TestCase): def setUp(self): self.client = TestClient(create_app()) def test_mutation_methods_rejected_on_core_paths(self): paths = ("/health", "/queue", "/projects", "/prompts", "/api/queue") for path in paths: for method in MUTATION_METHODS: with self.subTest(path=path, method=method): response = self.client.request(method, path) self.assertEqual(response.status_code, 405) self.assertEqual(response.json()["error"], "read-only-mvp") class TestWebuiOptionalModules(unittest.TestCase): """Exercise child-issue modules when present on the branch under test.""" def test_audit_validator_basics_when_present(self): mod = _import_optional("webui.audit_validator") if mod is None: self.skipTest("webui.audit_validator not merged on this branch") report = "## Controller Handoff\n- Task: review PR #1\n" self.assertEqual(mod.infer_task_kind(report), "review_pr") result = mod.audit_report(report) self.assertIn("grade", result) def test_gated_actions_fail_closed_when_present(self): mod = _import_optional("webui.gated_actions") if mod is None: self.skipTest("webui.gated_actions not merged on this branch") registry = mod.build_action_registry() for action in registry.actions: with self.subTest(action=action.action_id): self.assertFalse(action.enabled) result = mod.attempt_action("merge_pr", pr_number=1) self.assertFalse(result["success"]) def test_deployment_boundary_when_present(self): mod = _import_optional("webui.deployment_boundary") if mod is None: self.skipTest("webui.deployment_boundary not merged on this branch") assessment = mod.assess_bind_host("0.0.0.0") self.assertEqual(assessment.disposition, "refuse") class TestWebuiTestInventory(unittest.TestCase): def test_webui_test_modules_exist(self): modules = sorted(_REPO_ROOT.glob("tests/test_webui_*.py")) names = [path.name for path in modules] self.assertIn("test_webui_skeleton.py", names) self.assertIn("test_webui_project_registry.py", names) self.assertIn("test_webui_prompt_library.py", names) self.assertIn("test_webui_queue_dashboard.py", names) self.assertGreaterEqual(len(names), 5) class TestWebuiCiScripts(unittest.TestCase): def test_runner_scripts_exist(self): for name in ("test-webui", "ci-webui-check"): script = _REPO_ROOT / "scripts" / name self.assertTrue(script.is_file(), name) self.assertTrue(os.access(script, os.X_OK), name) if __name__ == "__main__": unittest.main()