feat: interactive review CLI

Per sub-project-2 plan Task 12 / spec §8.2. Walks proposed records (fields +
coords + rationale + best-effort ffmpeg preview), prompts accept/edit/skip/quit,
and persists each approval via save_catalog rewrite. I/O seams (input_fn/now_fn/
out) and --no-preview make the walk testable hermetically; decision logic lives
in the tested review core.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-04 06:34:34 -07:00
parent 3e07e86187
commit 376edd7819
2 changed files with 257 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
import io
import pytest
from hef.catalog import Record, load_catalog, save_catalog
from tools.review_cli import main
def make_record(**o):
base = dict(
id="a",
title="t",
source_url="u",
source_archive="nasa",
license="public_domain",
mode="video",
left=0,
right=0,
dark=0,
light=0,
duration_s=1,
file_path="nasa/a.mp4",
)
base.update(o)
return Record(**base)
def test_help_parses():
with pytest.raises(SystemExit) as e:
main(["--help"])
assert e.value.code == 0
def test_no_proposed_records(tmp_path):
catalog = tmp_path / "library.jsonl"
save_catalog([make_record(id="x", review_status="approved", reviewed_at="t")], catalog)
out = io.StringIO()
rc = main(["--catalog", str(catalog), "--no-preview"], out=out)
assert rc == 0
assert "no proposed records" in out.getvalue()
def test_accept_flips_to_approved(tmp_path):
catalog = tmp_path / "library.jsonl"
save_catalog([make_record(id="a", review_status="proposed")], catalog)
out = io.StringIO()
rc = main(
["--catalog", str(catalog), "--no-preview"],
input_fn=lambda prompt: "a",
now_fn=lambda: "2026-06-04T13:00:00+00:00",
out=out,
)
assert rc == 0
r = load_catalog(catalog)[0]
assert r.review_status == "approved"
assert r.reviewed_at == "2026-06-04T13:00:00+00:00"
def test_skip_leaves_proposed(tmp_path):
catalog = tmp_path / "library.jsonl"
save_catalog([make_record(id="a", review_status="proposed")], catalog)
rc = main(
["--catalog", str(catalog), "--no-preview"],
input_fn=lambda prompt: "s",
out=io.StringIO(),
)
assert rc == 0
assert load_catalog(catalog)[0].review_status == "proposed"
def test_edit_overrides_coordinates(tmp_path):
catalog = tmp_path / "library.jsonl"
save_catalog([make_record(id="a", review_status="proposed")], catalog)
answers = iter(["e", "4", "1", "2", "3"])
rc = main(
["--catalog", str(catalog), "--no-preview"],
input_fn=lambda prompt: next(answers),
now_fn=lambda: "t",
out=io.StringIO(),
)
assert rc == 0
r = load_catalog(catalog)[0]
assert (r.left, r.right, r.dark, r.light) == (4, 1, 2, 3)
assert r.review_status == "approved"
def test_quit_stops_walk(tmp_path):
catalog = tmp_path / "library.jsonl"
save_catalog(
[
make_record(id="a", review_status="proposed"),
make_record(id="b", review_status="proposed"),
],
catalog,
)
rc = main(
["--catalog", str(catalog), "--no-preview"],
input_fn=lambda prompt: "q",
out=io.StringIO(),
)
assert rc == 0
statuses = {r.id: r.review_status for r in load_catalog(catalog)}
assert statuses == {"a": "proposed", "b": "proposed"}