Add state management, WebSocket polling, html.js templating, and refactor pages
- lib/state.py: per-subsystem collectors with versioned state store - daemon/server.py: state refresh on request, batch routing updates - webui/static/hoover/html.js: new html tag template helper via htm.js - webui/static/hoover/websocket.js: real-time state change notifications - webui/static/hoover/vdom.js: VDOM improvements for keyed diff - All frontend pages refactored to use html templates - Add tests for state management and polling - Update docs and AGENTS.md
This commit is contained in:
+28
-28
@@ -122,7 +122,7 @@ class TestCheckNginxRunning:
|
||||
patch("subprocess.run", return_value=mock_result),
|
||||
patch.object(Path, "is_file", return_value=False),
|
||||
):
|
||||
passed, msg = _check_nginx_running()
|
||||
passed, _ = _check_nginx_running()
|
||||
assert passed is False
|
||||
|
||||
def test_via_pid_file(self):
|
||||
@@ -131,27 +131,27 @@ class TestCheckNginxRunning:
|
||||
def run_side_effect(cmd, **kwargs):
|
||||
raise FileNotFoundError()
|
||||
|
||||
pid_file = Path("/var/run/nginx.pid")
|
||||
with patch("subprocess.run", side_effect=run_side_effect):
|
||||
with patch.object(Path, "is_file") as mock_is_file:
|
||||
with patch.object(Path, "read_text", return_value="1234\n"):
|
||||
with (
|
||||
patch("subprocess.run", side_effect=run_side_effect),
|
||||
patch.object(Path, "read_text", return_value="1234\n"),
|
||||
):
|
||||
|
||||
def fake_is_file(self):
|
||||
if self == Path("/var/run/nginx.pid"):
|
||||
return True
|
||||
if str(self) == "/proc/1234/status":
|
||||
return True
|
||||
return Path(self).is_file()
|
||||
def fake_is_file(self):
|
||||
if self == Path("/var/run/nginx.pid"):
|
||||
return True
|
||||
if str(self) == "/proc/1234/status":
|
||||
return True
|
||||
return Path(self).is_file()
|
||||
|
||||
with patch.object(Path, "is_file", fake_is_file):
|
||||
passed, msg = _check_nginx_running()
|
||||
assert passed is True
|
||||
with patch.object(Path, "is_file", fake_is_file):
|
||||
passed, _ = _check_nginx_running()
|
||||
assert passed is True
|
||||
|
||||
|
||||
class TestCheckNginxConfig:
|
||||
def test_valid_config(self):
|
||||
with patch("lib.nginx.test_config", return_value=(True, "test passed")):
|
||||
passed, msg = _check_nginx_config()
|
||||
passed, _ = _check_nginx_config()
|
||||
assert passed is True
|
||||
|
||||
def test_invalid_config(self):
|
||||
@@ -175,7 +175,7 @@ class TestCheckFirewallPort80:
|
||||
with (
|
||||
patch("lib.common.run_proc", side_effect=proc_side_effect),
|
||||
):
|
||||
passed, msg = _check_firewall_port_80()
|
||||
passed, _ = _check_firewall_port_80()
|
||||
assert passed is True
|
||||
|
||||
def test_blocked_by_firewall(self):
|
||||
@@ -213,7 +213,7 @@ class TestCheckAcmeHomeWritable:
|
||||
acme_dir = tmp_path / "acme"
|
||||
acme_dir.mkdir()
|
||||
with patch("daemon.handlers.acme._ACME_HOME", acme_dir):
|
||||
passed, msg = _check_acme_home_writable()
|
||||
passed, _ = _check_acme_home_writable()
|
||||
assert passed is True
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ class TestCheckAcmeHomeWritable_Permissions:
|
||||
acme_dir.chmod(0o444)
|
||||
try:
|
||||
with patch("daemon.handlers.acme._ACME_HOME", acme_dir):
|
||||
passed, msg = _check_acme_home_writable()
|
||||
passed, _ = _check_acme_home_writable()
|
||||
assert passed is False
|
||||
finally:
|
||||
acme_dir.chmod(0o755)
|
||||
@@ -254,7 +254,7 @@ class TestCheckOpensslAvailable:
|
||||
|
||||
def test_not_found(self):
|
||||
with patch("shutil.which", return_value=None):
|
||||
passed, msg = _check_openssl_available()
|
||||
passed, _ = _check_openssl_available()
|
||||
assert passed is False
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ class TestCheckPort80Listening:
|
||||
mock_sock.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
with patch("socket.socket", return_value=mock_sock):
|
||||
passed, msg = _check_port_80_listening()
|
||||
passed, _ = _check_port_80_listening()
|
||||
assert passed is True
|
||||
|
||||
def test_not_listening(self):
|
||||
@@ -280,7 +280,7 @@ class TestCheckPort80Listening:
|
||||
mock_sock.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
with patch("socket.socket", return_value=mock_sock):
|
||||
passed, msg = _check_port_80_listening()
|
||||
passed, _ = _check_port_80_listening()
|
||||
assert passed is False
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@ class TestCheckAcmeAccount:
|
||||
),
|
||||
patch("subprocess.run", return_value=MagicMock(returncode=0, stdout="ok")),
|
||||
):
|
||||
passed, msg = _check_acme_account()
|
||||
passed, _ = _check_acme_account()
|
||||
assert passed is True
|
||||
|
||||
def test_via_account_conf(self, tmp_path):
|
||||
@@ -320,7 +320,7 @@ class TestCheckAcmeAccount:
|
||||
patch("daemon.handlers.acme._ACME_HOME", acme_dir),
|
||||
patch("subprocess.run", side_effect=run_side_effect),
|
||||
):
|
||||
passed, msg = _check_acme_account()
|
||||
passed, _ = _check_acme_account()
|
||||
assert passed is True
|
||||
|
||||
def test_not_configured(self, tmp_path):
|
||||
@@ -336,7 +336,7 @@ class TestCheckAcmeAccount:
|
||||
patch("daemon.handlers.acme._ACME_HOME", acme_dir),
|
||||
patch("subprocess.run", side_effect=run_side_effect),
|
||||
):
|
||||
passed, msg = _check_acme_account()
|
||||
passed, _ = _check_acme_account()
|
||||
assert passed is False
|
||||
|
||||
|
||||
@@ -351,7 +351,7 @@ class TestCheckDnsPublic:
|
||||
patch("socket.gethostbyname", return_value="192.168.1.1"),
|
||||
patch("subprocess.run", return_value=mock_result),
|
||||
):
|
||||
passed, msg = _check_dns_public("example.com")
|
||||
passed, _ = _check_dns_public("example.com")
|
||||
assert passed is True
|
||||
|
||||
def test_does_not_resolve(self):
|
||||
@@ -362,7 +362,7 @@ class TestCheckDnsPublic:
|
||||
patch("socket.gethostbyname", return_value="192.168.1.1"),
|
||||
patch("subprocess.run", return_value=mock_result),
|
||||
):
|
||||
passed, msg = _check_dns_public("example.com")
|
||||
passed, _ = _check_dns_public("example.com")
|
||||
assert passed is False
|
||||
|
||||
|
||||
@@ -502,7 +502,7 @@ class TestValidate:
|
||||
result = _validate("example.com")
|
||||
|
||||
assert result["ready"] is False
|
||||
nginx_check = [c for c in result["checks"] if c["name"] == "nginx_running"][0]
|
||||
nginx_check = next(c for c in result["checks"] if c["name"] == "nginx_running")
|
||||
assert nginx_check["passed"] is False
|
||||
assert nginx_check["blocking"] is True
|
||||
|
||||
@@ -566,7 +566,7 @@ class TestValidate:
|
||||
result = _validate("example.com")
|
||||
|
||||
assert result["ready"] is True
|
||||
dns_pub = [c for c in result["checks"] if c["name"] == "dns_public"][0]
|
||||
dns_pub = next(c for c in result["checks"] if c["name"] == "dns_public")
|
||||
assert dns_pub["passed"] is False
|
||||
assert dns_pub["blocking"] is False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user