from __future__ import annotations import unittest import tempfile from pathlib import Path from unittest.mock import patch from src.control_plane import SERVICE_SPECS, deployment_report, stack_summary class ControlPlaneServiceTests(unittest.TestCase): def test_service_registry_includes_required_modules(self) -> None: ids = {spec.service_id for spec in SERVICE_SPECS} self.assertTrue( { "mitmproxy", "vscode_server", "uptime_kuma", "pulseaudio", "kmscube", "openclaw", }.issubset(ids) ) def test_stack_summary_exposes_new_quick_links(self) -> None: summary = stack_summary() links = summary["quick_links"] for key in ( "mitmproxy", "vscode_server", "uptime_kuma", "pulseaudio_tcp", "openclaw", "neko_browser", "neko_browser_1", "neko_browser_2", "neko_browser_3", ): self.assertIn(key, links) neko_instances = summary["neko_instances"] self.assertEqual([item["id"] for item in neko_instances], [1, 2, 3]) self.assertEqual(links["neko_browser"], links["neko_browser_1"]) def test_deployment_report_marks_live_local_ready_when_checks_pass(self) -> None: with tempfile.TemporaryDirectory() as tmp: base_dir = Path(tmp) console_index = base_dir / "web-console" / "out" / "index.html" console_index.parent.mkdir(parents=True, exist_ok=True) console_index.write_text("ok", encoding="utf-8") stack_env = base_dir / "config" / "system-stack.env" stack_env.parent.mkdir(parents=True, exist_ok=True) stack_env.write_text("PORT=8001\nENABLE_LIVE_EDGE_PROXY=1\nENABLE_MDNS=1\n", encoding="utf-8") def probe(url: str, timeout: float = 2.5) -> dict[str, object]: return { "reachable": True, "http_code": 200, "latency_ms": 3.1, "error": "", } with patch("src.control_plane.BASE_DIR", base_dir): with patch("src.control_plane.STACK_ENV", stack_env): with patch("src.control_plane.load_stack_env", return_value={ "PORT": "8001", "ENABLE_LIVE_EDGE_PROXY": "1", "ENABLE_MDNS": "1", }): with patch("src.control_plane.stack_summary", return_value={ "hostname": "live", "mdns_host": "live.local", "ips": ["192.168.1.10"], }): with patch("src.control_plane._probe_http_get", side_effect=probe): with patch("src.control_plane._resolve_host_ipv4s", return_value=["192.168.1.10"]): report = deployment_report() self.assertTrue(report["ready"]) ids = {item["id"] for item in report["checks"]} self.assertIn("api_loopback", ids) self.assertIn("edge_loopback", ids) self.assertIn("mdns_edge", ids) self.assertEqual(report["urls"]["mdns_root"], "http://live.local/") def test_deployment_report_explains_edge_and_mdns_failures(self) -> None: with tempfile.TemporaryDirectory() as tmp: base_dir = Path(tmp) console_index = base_dir / "web-console" / "out" / "index.html" console_index.parent.mkdir(parents=True, exist_ok=True) console_index.write_text("ok", encoding="utf-8") stack_env = base_dir / "config" / "system-stack.env" stack_env.parent.mkdir(parents=True, exist_ok=True) stack_env.write_text("PORT=8001\nENABLE_LIVE_EDGE_PROXY=1\nENABLE_MDNS=1\n", encoding="utf-8") def probe(url: str, timeout: float = 2.5) -> dict[str, object]: if "127.0.0.1:8001/health" in url: return {"reachable": True, "http_code": 200, "latency_ms": 2.0, "error": ""} return {"reachable": False, "http_code": 0, "latency_ms": 2.0, "error": "connection refused"} with patch("src.control_plane.BASE_DIR", base_dir): with patch("src.control_plane.STACK_ENV", stack_env): with patch("src.control_plane.load_stack_env", return_value={ "PORT": "8001", "ENABLE_LIVE_EDGE_PROXY": "1", "ENABLE_MDNS": "1", }): with patch("src.control_plane.stack_summary", return_value={ "hostname": "live", "mdns_host": "live.local", "ips": ["192.168.1.10"], }): with patch("src.control_plane._probe_http_get", side_effect=probe): with patch("src.control_plane._resolve_host_ipv4s", return_value=[]): report = deployment_report() self.assertFalse(report["ready"]) joined = "\n".join(report["hints"]) self.assertIn("live.local does not resolve locally", joined) self.assertIn("reverse proxy is unhealthy", joined) if __name__ == "__main__": unittest.main()