"""The command line, end to end. Each command runs through main() against a throwaway repository.""" import shutil import sys from pathlib import Path import pytest import yaml from rtut.__main__ import main ROOT = Path(__file__).resolve().parent.parent @pytest.fixture def repo(tmp_path): for name, src in (("v1", "showcase-lighthouse"), ("v2", "showcase-lighthouse-v2")): d = tmp_path / "runbooks" / name; d.mkdir(parents=True) shutil.copy(ROOT / f"runbooks/{src}/pack.yaml", d / "pack.yaml") (tmp_path / "attribute-sets").mkdir() shutil.copy(ROOT / "attribute-sets/harbour-defaults.yaml", tmp_path / "attribute-sets/harbour-defaults.yaml") (tmp_path / "project-templates").mkdir() shutil.copy(ROOT / "project-templates/lighthouse-watch.yaml", tmp_path / "project-templates/lighthouse-watch.yaml") return tmp_path def run(capsys, *argv): code = main([str(a) for a in argv]) out = capsys.readouterr() return code, out.out, out.err def test_validate_and_list_show_maturity_not_exercise_status(repo, capsys): code, out, _ = run(capsys, "validate", repo) assert code == 0 and out.count("ok ") == 2 code, out, _ = run(capsys, "list", repo) assert code == 0 and "rtut.showcase-lighthouse\tnative\tdraft\t" in out and "exercise" not in out code, out, _ = run(capsys, "show", repo / "runbooks/v1/pack.yaml") assert code == 0 and "[draft]" in out and "engine=native" in out def test_render_and_score_and_maturity(repo, capsys): code, out, _ = run(capsys, "render", repo / "runbooks/v1/pack.yaml", "meet-the-keeper", "-p", "keeper=Bea") assert code == 0 and out.strip().startswith('echo "Bea climbs the stairs') code, out, _ = run(capsys, "score", repo) assert code == 0 and "rtut.showcase-lighthouse\ttried 0" in out and out.rstrip().endswith("[draft]") code, out, _ = run(capsys, "maturity", repo / "runbooks/v1/pack.yaml") assert code == 0 and out.startswith("rtut.showcase-lighthouse: draft") and "reviewed: False" in out def test_review_then_maturity(repo, capsys): pack = repo / "runbooks/v1/pack.yaml" assert run(capsys, "review", pack, "--by", "alice", "--note", "read it")[0] == 0 code, out, _ = run(capsys, "maturity", pack) assert "rtut.showcase-lighthouse: reviewed" in out and "reviewed: True" in out code, _, err = run(capsys, "review", pack, "--by", "two words") assert code == 1 and "one word" in err def test_run_commands_walk_a_v01_pack(repo, capsys): pack = repo / "runbooks/v1/pack.yaml" code, rid, _ = run(capsys, "run", "new", pack, "-o", "bernd", "-p", "logdir=/tmp/rtut-cli-x") rid = rid.strip() assert code == 0 and rid assert run(capsys, "run", "start", pack, rid, "read-the-sign")[0] == 0 assert run(capsys, "run", "verify", pack, rid, "read-the-sign", "--pass")[0] == 0 assert run(capsys, "run", "done", pack, rid, "read-the-sign")[0] == 0 code, out, _ = run(capsys, "run", "status", pack, rid) assert "done read-the-sign" in out and "todo meet-the-keeper" in out code, out, _ = run(capsys, "run", "finalize", pack, rid, "--outcome", "abandoned", "--note", "cli test") assert code == 0 and out.strip() == "abandoned" code, _, err = run(capsys, "run", "start", pack, rid, "meet-the-keeper") assert code == 1 and "finished" in err assert run(capsys, "run", "feedback", pack, rid, "--rating", "poor", "--note", "x")[0] == 1 # closed runs are locked def test_the_retired_finish_action_is_gone(repo): with pytest.raises(SystemExit): main(["run", "finish", str(repo / "runbooks/v1/pack.yaml"), "x"]) def test_project_commands(repo, capsys): code, out, _ = run(capsys, "project", "list", repo) assert code == 0 and "lighthouse-watch\ttemplate" in out code, out, _ = run(capsys, "project", "new", repo, "lighthouse-watch", "demo", "-e", "rehearsal", "--title", "Demo watch", "--description", "A demo", "-s", "keeper=Ada", "-s", "logdir=/tmp/rtut-cli-p") assert code == 0 and out.strip().endswith("projects/demo/project.yaml") made = yaml.safe_load((repo / "projects/demo/project.yaml").read_text()) assert made["title"] == "Demo watch" and made["description"] == "A demo" and made["template"] == "lighthouse-watch" # the template's example members need packs this fixture does not have, so validation reports it rather than crashing code, out, _ = run(capsys, "project", "validate", repo, "demo") assert code == 1 and "FAIL demo" in out and "rtut.example-hello not found" in out code, _, err = run(capsys, "project", "status", repo, "nope") assert code == 1 and "no project nope" in err def test_ui_and_launch_arguments_parse(repo): from rtut.__main__ import main as m with pytest.raises(SystemExit): m(["ui", "--port", "not-a-port"]) def test_check_validates_everything_declarative_and_reports_problems(repo, capsys): (repo / "runbooks/v1/pack.yaml").write_text((repo / "runbooks/v1/pack.yaml").read_text().replace("engine: native", "engine: nonsense")) code, out, _ = run(capsys, "check", repo) assert code == 1 and "FAIL rtut.showcase-lighthouse" in out and "ok rtut.showcase-lighthouse-v2" in out assert "attribute sets" in out and "ok harbour-defaults" in out and "templates" in out and "of" in out.splitlines()[-1] def test_check_passes_on_a_clean_repository_and_fails_on_an_empty_one(repo, capsys, tmp_path): code, out, _ = run(capsys, "check", repo) assert code == 0 and out.rstrip().endswith("4 of 4 ok") # two packs, one attribute set, one template empty = tmp_path / "empty"; empty.mkdir() code, _, err = run(capsys, "check", empty) assert code == 1 and "nothing to check" in err def test_the_schemas_ship_inside_the_package(repo): from rtut import pack, v2, attrs for path in (pack.SCHEMA_PATH, v2.SCHEMA_PATH, attrs.SCHEMA_DIR / "project.schema.json", attrs.SCHEMA_DIR / "attribute-set.schema.json"): assert path.exists() and "rtut/schema" in str(path) def test_seal_command_reports_sealed_broken_open_and_unsealed(repo, capsys, tmp_path): import json from rtut import pack as P, run as R1 pack = P.load(repo / "runbooks/v1/pack.yaml") r = R1.create(pack, "bernd") f = tmp_path / "rec.json" f.write_text(r.path.read_text()) code, out, _ = run(capsys, "seal", f) assert code == 2 and out.strip() == "open" # nothing to check yet R1.finalize(r, "abandoned") f.write_text(r.path.read_text()) code, out, _ = run(capsys, "seal", f) assert code == 0 and out.strip() == "sealed" d = json.loads(f.read_text()); d["operator"] = "forged"; f.write_text(json.dumps(d)) assert run(capsys, "seal", f)[:2] == (1, "broken\n") d = json.loads(r.path.read_text()); d.pop("seal"); f.write_text(json.dumps(d)) assert run(capsys, "seal", f)[:2] == (2, "unsealed\n") code, _, err = run(capsys, "seal", tmp_path / "missing.json") assert code == 1 and "error" in err