From b6fb635ef4d7f3e1426e851dea7fe6d6333ee0ba Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Thu, 4 Jun 2026 06:23:01 -0700 Subject: [PATCH] feat: catalog-level validate_catalog + index_by_id (unique ids) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per sub-project-2 plan Task 2 / spec ยง3. Purely additive to hef.catalog; no existing symbol changes. validate_catalog runs per-record validate() then asserts id uniqueness; index_by_id gives by-id lookup the player also wants. Co-Authored-By: Claude Opus 4.8 (1M context) --- hef/catalog.py | 17 ++++++++++++ tests/test_catalog_integrity.py | 48 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 tests/test_catalog_integrity.py diff --git a/hef/catalog.py b/hef/catalog.py index 56aac04..0e61c54 100644 --- a/hef/catalog.py +++ b/hef/catalog.py @@ -126,3 +126,20 @@ def append_record(record, path) -> None: path = Path(path) with path.open("a", encoding="utf-8") as fh: fh.write(json.dumps(record_to_dict(record), ensure_ascii=False) + "\n") + + +def index_by_id(records) -> dict: + """Map id -> Record, raising CatalogError on a duplicate id.""" + idx: dict = {} + for r in records: + if r.id in idx: + raise CatalogError(f"duplicate record id: {r.id!r}") + idx[r.id] = r + return idx + + +def validate_catalog(records) -> None: + """Validate every record AND cross-record invariants (currently: unique ids).""" + for r in records: + validate(r) + index_by_id(records) # raises on duplicate id diff --git a/tests/test_catalog_integrity.py b/tests/test_catalog_integrity.py new file mode 100644 index 0000000..c12c5a3 --- /dev/null +++ b/tests/test_catalog_integrity.py @@ -0,0 +1,48 @@ +import pytest + +from hef.catalog import Record, validate_catalog, index_by_id, CatalogError + + +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="p", + ) + base.update(o) + return Record(**base) + + +def test_validate_catalog_accepts_unique_ids(): + validate_catalog([make_record(id="a"), make_record(id="b")]) # no raise + + +def test_validate_catalog_rejects_duplicate_id(): + with pytest.raises(CatalogError) as e: + validate_catalog([make_record(id="dup"), make_record(id="dup")]) + assert "dup" in str(e.value) + + +def test_validate_catalog_validates_each_record(): + with pytest.raises(CatalogError): + validate_catalog([make_record(left=9)]) + + +def test_index_by_id_round_trips(): + recs = [make_record(id="a"), make_record(id="b")] + idx = index_by_id(recs) + assert idx["a"].id == "a" and idx["b"].id == "b" + + +def test_index_by_id_rejects_duplicates(): + with pytest.raises(CatalogError): + index_by_id([make_record(id="x"), make_record(id="x")])