Add smiley feedback collector with comment after a completed run (RBT-WP-0004-T12)
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
This commit is contained in:
parent
7d0249ed51
commit
c17b43264d
6 changed files with 167 additions and 2 deletions
41
tests/test_feedback.py
Normal file
41
tests/test_feedback.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from rtut import pack as P, run as R
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pk(tmp_path):
|
||||
d = tmp_path / "runbooks" / "hello"; d.mkdir(parents=True)
|
||||
shutil.copy(ROOT / "runbooks/example-hello/pack.yaml", d / "pack.yaml")
|
||||
return P.load(d / "pack.yaml")
|
||||
|
||||
|
||||
def test_feedback_latest_wins_and_history_kept(pk):
|
||||
r = R.create(pk, "bernd")
|
||||
R.add_feedback(r, "poor", "confusing start")
|
||||
R.add_feedback(r, "good", "fine after a second look")
|
||||
r2 = R.load(pk, r.data["run_id"])
|
||||
assert len(r2.data["feedback"]) == 2
|
||||
assert R.latest_feedback(r2)["rating"] == "good"
|
||||
|
||||
|
||||
def test_feedback_rejects_bad_rating_and_secrets(pk):
|
||||
r = R.create(pk, "bernd")
|
||||
with pytest.raises(R.RunError):
|
||||
R.add_feedback(r, "great")
|
||||
with pytest.raises(R.RunError):
|
||||
R.add_feedback(r, "good", "-----BEGIN RSA PRIVATE KEY-----")
|
||||
assert "feedback" not in R.load(pk, r.data["run_id"]).data
|
||||
|
||||
|
||||
def test_all_feedback_lists_newest_first(pk):
|
||||
a = R.create(pk, "ann"); b = R.create(pk, "bob"); R.create(pk, "cy") # cy gives none
|
||||
R.add_feedback(a, "good", "one")
|
||||
R.add_feedback(b, "poor", "two")
|
||||
got = R.all_feedback(pk)
|
||||
assert [g["operator"] for g in got] == ["bob", "ann"]
|
||||
|
|
@ -178,3 +178,48 @@ def test_done_steps_fold_to_the_title_line_and_others_stay_open(srv):
|
|||
assert "<details class='step done'>" in body # done: no 'open' attribute, so folded
|
||||
assert "1. Read the poem" in body.split("<details class='step done'>")[1].split("</summary>")[0] # title line stays in the summary
|
||||
assert "Comment: fine" in body.split("<details class='step done'>")[1].split("</details>")[0] # the rest is inside the fold
|
||||
|
||||
|
||||
def _finish_showcase(srv, t, base):
|
||||
import subprocess
|
||||
from rtut import run as R
|
||||
rid = base.rsplit("/", 1)[1]
|
||||
r = R.load(srv.app.packs[int(base.split("/")[2])], rid)
|
||||
for s in r.pack.data["steps"]:
|
||||
sid = s["id"]
|
||||
req(srv, "POST", f"{base}/{sid}/start", {"_t": t})
|
||||
cmd = R.command_for(r, sid)
|
||||
if cmd:
|
||||
subprocess.run(cmd, shell=True, check=True, capture_output=True)
|
||||
if s["verify"]["expect"] == "manual":
|
||||
req(srv, "POST", f"{base}/{sid}/verify", {"_t": t, "ok": "1"})
|
||||
else:
|
||||
req(srv, "POST", f"{base}/{sid}/verify", {"_t": t, "yes": "1"})
|
||||
ev = {f"e_{n}": "1" for n in s.get("evidence", [])}
|
||||
req(srv, "POST", f"{base}/{sid}/done", {"_t": t, **ev})
|
||||
|
||||
|
||||
def test_feedback_form_after_all_steps_done_and_recording(srv):
|
||||
t, loc, _ = _showcase_run(srv)
|
||||
base = loc.split("#")[0]
|
||||
_, _, body = req(srv, "GET", base)
|
||||
assert "How was this runbook?" not in body # not before the run is complete
|
||||
_finish_showcase(srv, t, base)
|
||||
_, _, body = req(srv, "GET", base)
|
||||
assert "All steps done." in body and "How was this runbook?" in body
|
||||
assert body.index("All steps done.") < body.index("How was this runbook?")
|
||||
assert "🙂" in body and "😐" in body and "🙁" in body and "name=comment" in body
|
||||
st, loc2, _ = req(srv, "POST", base + "/feedback", {"_t": t, "rating": "neutral", "comment": "step 3 was unclear"})
|
||||
assert st == 303 and loc2.endswith("#feedback")
|
||||
_, _, body = req(srv, "GET", base)
|
||||
assert "Thank you." in body and "step 3 was unclear" in body
|
||||
|
||||
|
||||
def test_feedback_validation(srv):
|
||||
t, loc, _ = _showcase_run(srv)
|
||||
base = loc.split("#")[0]
|
||||
_finish_showcase(srv, t, base)
|
||||
assert req(srv, "POST", base + "/feedback", {"_t": t, "rating": "amazing"})[0] == 400
|
||||
st, _, body = req(srv, "POST", base + "/feedback", {"_t": t, "rating": "good", "comment": "hvs.ABCDEFGHIJKLMN"})
|
||||
assert st == 400 and "secret-looking" in body
|
||||
assert req(srv, "POST", base + "/feedback", {"rating": "good"})[0] == 403 # CSRF token required
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue