From 2db5617426b8b680f97c22bdc5171a55ba56e0f3 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 16 Aug 2026 20:18:27 +0200 Subject: [PATCH] Add live Butler capabilities safety map --- tests/test_app.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_app.py b/tests/test_app.py index 726d11d..a9a0ae1 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -46,6 +46,33 @@ def test_health_exposes_current_version(): assert response.json()["version"] == app.VERSION == "2.3.5" +def test_capabilities_is_live_machine_readable_safety_map(): + with TestClient(app.app) as client: + response = client.get( + "/capabilities", + headers={"Authorization": "Bearer test-token"}, + ) + assert response.status_code == 200 + payload = response.json() + by_operation = {(item["method"], item["path"]): item for item in payload["operations"]} + assert ("GET", "/network/wireguard/{host}") in by_operation + assert by_operation[("GET", "/network/wireguard/{host}")]["mode"] == "read_only" + removal = by_operation[("DELETE", "/network/wireguard/{host}/peer")] + assert removal["mode"] == "mutation" + assert removal["dry_run"] is True + assert removal["critical"] is True + assert by_operation[("DELETE", "/vm/destroy/{vmid}")]["dry_run"] is True + assert all(item["path"] != "/{service}/{path}" for item in payload["operations"]) + assert payload["model_contract"]["instruction"].startswith("Prefer read_only") + + +def test_info_advertises_capabilities_endpoint(): + with TestClient(app.app) as client: + response = client.get("/info", headers={"Authorization": "Bearer test-token"}) + assert response.status_code == 200 + assert response.json()["endpoints"]["capabilities"] == "/capabilities" + + def test_wireguard_status_returns_redacted_live_state(monkeypatch): payload = { "interface": "wg0",