import httpx from app.agent_runs import AgentRunRepository from app.constants import CHECKMK_DT_ENABLED, CHECKMK_DT_SECRET, CHECKMK_DT_USER, FLAG_ON from app.downtime import CheckmkDowntimeClient from tests.test_web import (VIEWER, admin, app, client, csrf_from, login_ok, post, # noqa: F401 users, viewer) DT_SECRET = "downtime-geheimnis-xyz" def make_run(app, question="Warum CPU?", steps=None, abort=None): repo = AgentRunRepository(app.state.db) steps = steps if steps is not None else [ {"number": 1, "tool": "get_service_status", "arguments": {"host": "web01"}, "result": "", "duration": 0.5, "error": None}] return repo.insert(1_700_000_000, "telegram", "111", "ask", question, "Antwort x", abort, steps) def test_agent_pages_require_admin(client, app, viewer): run_id = make_run(app) assert client.get("/agent").status_code == 303 login_ok(client, VIEWER) assert client.get("/agent").status_code == 403 assert client.get(f"/agent/{run_id}").status_code == 403 assert "/agent" not in client.get("/").text def test_agent_list_and_detail_for_admin_escaped(client, app, admin): run_id = make_run(app, abort="step_limit") login_ok(client) assert 'href="/agent"' in client.get("/").text page = client.get("/agent") assert page.status_code == 200 and f"/agent/{run_id}" in page.text and "Schrittlimit" in page.text assert "CPU" not in page.text and "<b>CPU</b>" in page.text detail = client.get(f"/agent/{run_id}") assert detail.status_code == 200 and "get_service_status" in detail.text and "host=web01" in detail.text assert "" not in detail.text and "<script>" in detail.text assert "Antwort <i>x</i>" in detail.text and "")[0] def test_downtime_section_saves_encrypted_secret_and_never_shows_it(client, app, admin): login_ok(client) assert post(client, "/settings/checkmk_dt", "/settings/checkmk_dt", **dt_form()).status_code == 303 store = app.state.store assert store.get(CHECKMK_DT_ENABLED) == FLAG_ON and store.get(CHECKMK_DT_USER) == "dtuser" assert store.get(CHECKMK_DT_SECRET) == DT_SECRET raw = app.state.db.query("SELECT value, is_secret FROM settings WHERE key=?", (CHECKMK_DT_SECRET,))[0] assert raw["is_secret"] == 1 and DT_SECRET not in raw["value"] assert DT_SECRET not in client.get("/settings/checkmk_dt").text audit_rows = app.state.db.query("SELECT old_value, new_value FROM audit WHERE target=?", (CHECKMK_DT_SECRET,)) assert audit_rows and all(DT_SECRET not in str(tuple(r)) for r in audit_rows) assert "gesetzt" in client.get("/settings/checkmk_dt").text def test_downtime_section_validation(client, app, admin): login_ok(client) for override in ({"checkmk_dt_max_hours": "0"}, {"checkmk_dt_max_hours": "999"}, {"checkmk_dt_max_hours": "x"}, {"checkmk_dt_user": "", "checkmk_dt_secret": ""}, {"checkmk_dt_user": "a b"}): response = post(client, "/settings/checkmk_dt", "/settings/checkmk_dt", **dt_form(**override)) assert response.status_code == 422, override assert app.state.store.get(CHECKMK_DT_ENABLED) is None def test_downtime_default_is_off(app): assert app.state.store.get(CHECKMK_DT_ENABLED) is None assert app.state.runtime.chatops.downtime.enabled() is False def test_downtime_connection_test_uses_downtime_user_and_get_only(client, app, admin, monkeypatch): seen = [] def handler(request): seen.append((request.method, request.url.path, request.headers["authorization"])) return httpx.Response(200, json={"versions": {"checkmk": "2.3.0"}}) original = CheckmkDowntimeClient.__init__ def patched(self, *args, **kwargs): kwargs["transport"] = httpx.MockTransport(handler) original(self, *args, **kwargs) monkeypatch.setattr(CheckmkDowntimeClient, "__init__", patched) store = app.state.store store.set("checkmk.url", "https://cmk.example.de", "t") store.set("checkmk.site", "prod", "t") login_ok(client) token = csrf_from(client, "/settings/checkmk_dt") response = client.post("/settings/checkmk_dt/test", data={"csrf": token, **dt_form()}) body = response.json() assert body["ok"] is True and "nur lesend" in body["detail"] and DT_SECRET not in body["detail"] assert seen == [("GET", "/prod/check_mk/api/1.0/version", f"Bearer dtuser {DT_SECRET}")]