From 5723eebfb47d657265a553ac908c80065db87a30 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 16 Aug 2026 20:56:24 +0200 Subject: [PATCH] feat(ui): update tests/test_app.py --- tests/test_app.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/test_app.py b/tests/test_app.py index 645220a..82356fd 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -46,6 +46,46 @@ def test_health_exposes_current_version(): assert response.json()["version"] == app.VERSION == "2.3.5" +def test_ui_serves_self_contained_operator_console(): + with TestClient(app.app) as client: + response = client.get("/ui") + assert response.status_code == 200 + assert "Pfannkuchen Butler" in response.text + assert 'id="operations-grid"' in response.text + assert 'id="doctor-form"' in response.text + assert 'id="preflight-form"' in response.text + assert "localStorage" not in response.text + + +def test_ui_asset_is_mounted_read_only_in_compose(): + from pathlib import Path + import yaml + compose = yaml.safe_load(Path(app.__file__).with_name("compose.yaml").read_text(encoding="utf-8")) + mounts = compose["services"]["homelab-butler"]["volumes"] + assert "./ui.html:/app/ui.html:ro" in mounts + + +def test_ui_session_login_uses_httponly_cookie_and_csrf(): + app._ui_sessions.clear() + with TestClient(app.app) as client: + denied = client.post("/ui/login", json={"token": "wrong"}) + assert denied.status_code == 401 + + login = client.post("/ui/login", json={"token": "test-token"}) + assert login.status_code == 200 + assert "HttpOnly" in login.headers.get("set-cookie", "") + csrf = client.cookies.get("butler_csrf") + assert csrf + + capabilities = client.get("/capabilities") + assert capabilities.status_code == 200 + + blocked = client.post("/config/reload") + assert blocked.status_code == 403 + allowed = client.post("/config/reload", headers={"X-CSRF-Token": csrf}) + assert allowed.status_code == 200 + + def test_capabilities_is_live_machine_readable_safety_map(): with TestClient(app.app) as client: response = client.get( @@ -74,6 +114,7 @@ def test_info_advertises_capabilities_endpoint(): assert response.json()["endpoints"]["doctor"] == "/doctor/{target}" assert response.json()["endpoints"]["drift"] == "/drift" assert response.json()["endpoints"]["maintenance_preflight"] == "/maintenance/preflight" + assert response.json()["endpoints"]["ui"] == "/ui" def test_audit_persists_and_redacts_secrets(tmp_path, monkeypatch):