runbook-tutorials/tests/test_ui.py
tegwick b47c948a7e
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Work Records / validate (push) Has been cancelled
Add lighthouse showcase pack (v0.1 runnable, v0.2 draft); fix manual-check cycle and blocked-step feedback (RBT-WP-0004-T05)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 295952@bnt-lap001
Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
2026-09-29 10:36:29 +02:00

96 lines
4 KiB
Python

import http.client
import re
import shutil
import threading
from pathlib import Path
from urllib.parse import urlencode
import pytest
from rtut import ui
ROOT = Path(__file__).resolve().parent.parent
@pytest.fixture
def srv(tmp_path):
d = tmp_path / "runbooks" / "hello"; d.mkdir(parents=True)
shutil.copy(ROOT / "runbooks/example-hello/pack.yaml", d / "pack.yaml")
s = ui.serve([tmp_path], 0)
threading.Thread(target=s.serve_forever, daemon=True).start()
yield s
s.shutdown()
def req(s, method, path, form=None, host="127.0.0.1"):
c = http.client.HTTPConnection("127.0.0.1", s.server_address[1])
body = urlencode(form) if form is not None else None
c.request(method, path, body, {"Host": host, "Content-Type": "application/x-www-form-urlencoded"})
r = c.getresponse()
return r.status, r.getheader("Location"), r.read().decode()
def test_index_lists_pack(srv):
st, _, body = req(srv, "GET", "/")
assert st == 200 and "Example - check a host answers" in body
def test_foreign_host_and_missing_token_refused(srv):
assert req(srv, "GET", "/", host="evil.example")[0] == 403
assert req(srv, "POST", "/run/new/0", {"operator": "bernd"})[0] == 403
def test_walk_a_run(srv):
t = srv.app.token
st, loc, _ = req(srv, "POST", "/run/new/0", {"_t": t, "operator": "bernd", "p_host": "localhost", "p_count": "1"})
assert st == 303 and loc.startswith("/run/0/")
assert req(srv, "POST", loc + "/ping/start", {"_t": t})[0] == 303
st, _, body = req(srv, "GET", loc)
assert "ping -c 1 localhost" in body and "Run check" in body
req(srv, "POST", loc + "/ping/verify", {"_t": t, "yes": "1"})
st, _, body = req(srv, "GET", loc)
if "passed" in body and "failed" not in body.split("Threat")[0].replace("Mark failed", ""):
assert req(srv, "POST", loc + "/ping/done", {"_t": t})[0] == 303
assert "All steps done" in req(srv, "GET", loc)[2]
def test_unsafe_parameter_rejected_and_escaped(srv):
t = srv.app.token
st, _, body = req(srv, "POST", "/run/new/0", {"_t": t, "operator": "bernd", "p_host": "<script>x;"})
assert st == 400 and "<script>x" not in body
def test_secret_in_note_refused(srv):
t = srv.app.token
_, loc, _ = req(srv, "POST", "/run/new/0", {"_t": t, "operator": "bernd"})
req(srv, "POST", loc + "/ping/start", {"_t": t})
st, _, body = req(srv, "POST", loc + "/ping/fail", {"_t": t, "note": "hvs.ABCDEFGHIJKLMN"})
assert st == 400 and "secret-looking" in body
def test_manual_check_cycle_and_blocked_reason(srv):
"""Yes/Not-yet buttons, 'Mark done' to lock in, numbering, and why later steps are blocked."""
import shutil
t = srv.app.token
# use the showcase pack, which starts with a manual step
d = Path(srv.app.packs[0].path).parents[2] / "runbooks" / "show"; d.mkdir(parents=True)
shutil.copy(ROOT / "runbooks/showcase-lighthouse/pack.yaml", d / "pack.yaml")
from rtut import pack as P
srv.app.packs.append(P.load(d / "pack.yaml"))
i = len(srv.app.packs) - 1
_, loc, _ = req(srv, "POST", f"/run/new/{i}", {"_t": t, "operator": "bernd"})
_, _, body = req(srv, "GET", loc)
assert "0 of 8 steps done" in body and "1. Read the poem" in body
assert "Waiting for: step 1" in body # blocked steps explain themselves
req(srv, "POST", loc + "/read-the-sign/start", {"_t": t})
_, _, body = req(srv, "GET", loc)
assert "Yes, it is" in body and "Not yet" in body and "/read-the-sign/done" not in body
req(srv, "POST", loc + "/read-the-sign/verify", {"_t": t, "ok": "0"})
_, _, body = req(srv, "GET", loc)
assert "Check not passed" in body and "/read-the-sign/done" not in body
req(srv, "POST", loc + "/read-the-sign/verify", {"_t": t, "ok": "1"})
_, _, body = req(srv, "GET", loc)
assert "Check passed" in body and "/read-the-sign/done" in body
req(srv, "POST", loc + "/read-the-sign/done", {"_t": t})
_, _, body = req(srv, "GET", loc)
assert "1 of 8 steps done" in body and "Start this step" in body