Active user attribute set, latest activities, guarded changes to used values, artefact links, durations and averages; irreversible showcase step
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Work Records / validate (push) Has been cancelled

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:
tegwick 2026-09-29 15:50:56 +02:00
parent 6716cee921
commit 8d62c1fbe8
22 changed files with 1025 additions and 35 deletions

View file

@ -0,0 +1,93 @@
import os
import shutil
from pathlib import Path
import pytest
from rtut import activity, artefacts as A, maturity as M, pack as P, run as R, run2
ROOT = Path(__file__).resolve().parent.parent
def repo_with(tmp_path, *names):
packs = []
for n in names:
d = tmp_path / "runbooks" / n; d.mkdir(parents=True)
shutil.copy(ROOT / ("runbooks/showcase-lighthouse-v2/pack.yaml" if n.endswith("2") else "runbooks/showcase-lighthouse/pack.yaml"), d / "pack.yaml")
packs.append(P.load(d / "pack.yaml"))
return packs
# ---- activity -----------------------------------------------------------------------------------------------------
def test_latest_activities_are_newest_first_across_packs_and_cover_the_main_events(tmp_path):
v1, v2 = repo_with(tmp_path, "one", "two2")
r = R.create(v1, "ann")
R.start(r, "read-the-sign"); R.verify(r, "read-the-sign", manual_ok=True); R.complete(r, "read-the-sign")
R.add_feedback(r, "good", "smooth")
M.add_review(v1, "bob", "read it")
q = run2.create(v2, "cy", {"logdir": "/tmp/rtut-act-x"})
run2.set_datapoint(q, "weather", "clear")
run2.start(q, "read-the-sky"); run2.fail(q, "read-the-sky", "x")
R.finalize(r, "abandoned")
ev = activity.latest([v1, v2], 50)
assert [e["at"] for e in ev] == sorted((e["at"] for e in ev), reverse=True)
text = " | ".join(f"{e['who']}: {e['text']}" for e in ev)
for frag in ("ann: started a run (manual mode)", "ann: finished step “Read the poem over the door”", "ann: rated the run good: smooth",
"bob: reviewed the runbook: read it", "cy: started a run (manual mode)", "cy: failed step “Read the sky”",
"ann: finished the run as abandoned"):
assert frag in text, frag
assert all(e["link"] for e in ev) and {e["pack"] for e in ev} == {v1.data["title"], v2.data["title"]}
assert len(activity.latest([v1, v2], 3)) == 3
def test_overrides_and_changed_values_are_visible_in_the_feed(tmp_path):
(v2,) = repo_with(tmp_path, "two2")
q = run2.create(v2, "cy", {"logdir": "/tmp/rtut-act-y"})
run2.start(q, "warn", alt="sound-horn", override_reason="want to see", acknowledge_risk=True)
text = " | ".join(e["text"] for e in activity.latest([v2], 20))
assert "override: warn (want to see)" in text
# ---- artefact serving rules ----------------------------------------------------------------------------------------
def test_a_plain_text_file_may_be_served(tmp_path):
f = tmp_path / "logbook.txt"; f.write_text("Night watch by Ada\n")
path, why = A.check(str(f))
assert path == f.resolve() and why == ""
data, ctype, is_text = A.content(path)
assert data == b"Night watch by Ada\n" and ctype.startswith("text/plain") and is_text
@pytest.mark.parametrize("make,frag", [
(lambda t: None, "not known yet"),
(lambda t: str(t / "missing.txt"), "does not exist"),
(lambda t: str(t), "not a regular file"),
(lambda t: "relative/file.txt", "absolute"),
(lambda t: "https://example.org/x", "link"),
])
def test_refused_locations(tmp_path, make, frag):
path, why = A.check(make(tmp_path))
assert path is None and frag in why
def test_sensitive_files_secrets_and_big_files_are_never_served(tmp_path):
ssh = tmp_path / ".ssh"; ssh.mkdir(); (ssh / "notes.txt").write_text("hello")
assert "sensitive" in A.check(str(ssh / "notes.txt"))[1]
key = tmp_path / "server.key"; key.write_text("x")
assert "sensitive" in A.check(str(key))[1]
runs = tmp_path / "runbook-runs"; runs.mkdir(); (runs / "r.json").write_text("{}")
assert "sensitive" in A.check(str(runs / "r.json"))[1]
leaky = tmp_path / "out.txt"; leaky.write_text("token " + "hvs." + "ABCDEFGHIJKLMN")
assert "secret" in A.check(str(leaky))[1]
big = tmp_path / "big.bin"; big.write_bytes(b"0" * (A.MAX_BYTES + 1))
assert "larger" in A.check(str(big))[1]
link = tmp_path / "link.txt"; os.symlink(str(ssh / "notes.txt"), link)
assert "sensitive" in A.check(str(link))[1] # a symlink is judged by where it really points
def test_binary_files_are_offered_only_as_downloads(tmp_path):
f = tmp_path / "blob.bin"; f.write_bytes(b"\xff\xfe\x00\x01")
path, _ = A.check(str(f))
_, ctype, is_text = A.content(path)
assert not is_text and "text/plain" not in ctype