App shell with sidebar and theme switch; attribute-set management; project rename; new pages for projects, runbooks and activity
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 5s
Work Records / validate (push) Successful in 12s

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 17:38:06 +02:00
parent a6ee6a0a6e
commit 6579618737
9 changed files with 663 additions and 13 deletions

View file

@ -249,3 +249,67 @@ def test_project_without_a_given_name_uses_its_id_never_the_templates_title(repo
J.instantiate(reg, "lighthouse-watch", "ab", "staging")
with pytest.raises(ValueError, match="secret"):
J.instantiate(reg, "lighthouse-watch", "leaky-one", "staging", description="token hvs.ABCDEFGHIJKLMN")
# ---- managing attribute sets and editing projects --------------------------------------------------------------------
def test_used_by_finds_projects_templates_targets_and_extending_sets(repo):
from rtut.attrs import used_by
reg = project(repo) # north-watch uses stormy-coast, which extends harbour-defaults
u = used_by(reg, "stormy-coast")
assert u == {"projects": ["north-watch"], "templates": [], "sets": []}
u = used_by(reg, "harbour-defaults")
assert u["templates"] == ["lighthouse-watch"] and u["sets"] == ["stormy-coast"]
def test_save_set_writes_validates_and_normalizes(repo):
from rtut.attrs import save_set
reg = Registry([repo])
path = save_set(reg, repo, "team-defaults", "Team defaults", ["harbour-defaults"],
{"lamp": {"value": "electric"}, "keeper": {"requirement": "required", "note": "who"}, "empty_note": {"note": "later"}})
made = yaml.safe_load(path.read_text())
assert path == repo / "attribute-sets/team-defaults.yaml" and made["extends"] == ["harbour-defaults"]
assert made["attributes"]["lamp"] == "electric" # a lone value is written as a scalar
assert made["attributes"]["keeper"] == {"requirement": "required", "note": "who"}
reg = Registry([repo])
assert reg.sets["team-defaults"].ok and fold(set_layers(reg, "team-defaults"))["lamp"]["value"] == "electric"
@pytest.mark.parametrize("kwargs,frag", [
({"set_id": "Bad Id"}, "lower-case"), ({"title": "ab"}, "3 characters"), ({"extends": ["ghost"]}, "unknown attribute set"),
({"extends": ["team-x"]}, "itself"), ({"attributes": {"Bad-Name": {"value": "x"}}}, "bad attribute name"),
({"attributes": {"a": {"value": "hvs.ABCDEFGHIJKLMN"}}}, "secret"), ({"attributes": {"a": {"requirement": "maybe", "value": "x"}}}, "requirement"),
({"attributes": {"a": {}}}, "give a value"),
])
def test_save_set_refusals(repo, kwargs, frag):
from rtut.attrs import save_set
args = {"set_id": "team-x", "title": "Team x", "extends": [], "attributes": {"a": {"value": "1"}}, **kwargs}
with pytest.raises(ValueError, match=frag):
save_set(Registry([repo]), repo, **args)
assert not (repo / "attribute-sets/team-x.yaml").exists()
def test_save_set_refuses_a_cycle(repo):
from rtut.attrs import save_set
reg = Registry([repo])
with pytest.raises(ValueError, match="circular"):
save_set(reg, repo, "harbour-defaults", "Harbour defaults", ["stormy-coast"], {"lamp": {"value": "x"}}, reg.sets["harbour-defaults"].path)
def test_update_project_renames_and_keeps_everything_else(repo):
reg = project(repo)
doc = reg.projects["north-watch"]
before = yaml.safe_load(doc.path.read_text())
J.update_project(doc, "North harbour watch", "Our harbour", "Both lights burn")
after = yaml.safe_load(doc.path.read_text())
assert after["title"] == "North harbour watch" and after["description"] == "Our harbour" and after["goal"] == "Both lights burn"
assert after["runbooks"] == before["runbooks"] and after["attributes"] == before["attributes"] and after["id"] == "north-watch"
assert list(after)[:5] == ["spec", "id", "title", "description", "goal"]
J.update_project(Registry([repo]).projects["north-watch"], "North harbour watch", "", "")
assert "description" not in yaml.safe_load(doc.path.read_text())
for bad, frag in (("ab", "3 characters"), ("x" * 5 + "hvs.ABCDEFGHIJKLMN", "secret")):
with pytest.raises(ValueError, match=frag):
J.update_project(Registry([repo]).projects["north-watch"], bad)
reg = Registry([repo])
with pytest.raises(ValueError, match="only a project"):
J.update_project(reg.templates["lighthouse-watch"], "Renamed template")