"""Phase 2 — Parent existence validation in write_commit. Invariant: a commit whose parent_commit_id or parent2_commit_id does not exist in the store must be rejected before any bytes are written to disk. A dangling parent pointer is undetectable at read time and silently truncates history traversal — walks stop at the gap instead of at the true root. Testing tiers ------------- Unit MissingParentError raised for unknown parent / parent2 Unit No error for root commits (None parents) Unit No MissingParentError when parent exists in the store Integration commit_exists returns False after a rejected write Data no commit file appears on disk after a MissingParentError Security only MissingParentError (ValueError subclass) is raised — not silent success that writes a corrupt file """ from __future__ import annotations import datetime import pathlib import pytest from muse.core.types import long_id from muse.core.object_store import object_path from muse.core.commits import ( CommitRecord, MissingParentError, commit_exists, write_commit, ) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- _REPO_ID = "repo-phase2-test" _BRANCH = "main" _SNAP_ID = long_id("a" * 64) def _fake_commit_id(tag: str) -> str: return long_id(tag.encode().hex().ljust(64, "0")[:64]) def _make_commit( commit_id: str, parent_commit_id: str | None = None, parent2_commit_id: str | None = None, ) -> CommitRecord: return CommitRecord( commit_id=commit_id, branch=_BRANCH, snapshot_id=_SNAP_ID, message="test", committed_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc), parent_commit_id=parent_commit_id, parent2_commit_id=parent2_commit_id, ) def _plant_commit_file(repo: pathlib.Path, commit_id: str) -> None: """Write a stub commit directly into the unified object store so commit_exists() returns True. Bypasses hash verification intentionally — these are fake IDs used only to satisfy the parent-existence check in write_commit. """ path = object_path(repo, commit_id) path.parent.mkdir(parents=True, exist_ok=True) payload = b'{"commit_id":"' + commit_id.encode() + b'"}' path.write_bytes(b"commit " + str(len(payload)).encode() + b"\0" + payload) # --------------------------------------------------------------------------- # Unit — root commits (no parent) never raise MissingParentError # --------------------------------------------------------------------------- class TestRootCommitNeverRaisesParentError: def test_none_parents_not_rejected_for_parent_reason(self, tmp_path: pathlib.Path) -> None: """Root commit with no parents must never raise MissingParentError.""" cid = _fake_commit_id("root") rec = _make_commit(cid, parent_commit_id=None, parent2_commit_id=None) # Hash mismatch (content-address check) is expected — we only verify the # PARENT existence check does not fire. with pytest.raises((ValueError, OSError)) as exc_info: write_commit(tmp_path, rec) assert not isinstance(exc_info.value, MissingParentError), ( "Root commit raised MissingParentError — parent check incorrectly fired" ) # --------------------------------------------------------------------------- # Unit — missing parent_commit_id → MissingParentError before disk write # --------------------------------------------------------------------------- class TestMissingParentRejected: def test_unknown_parent_raises_missing_parent_error(self, tmp_path: pathlib.Path) -> None: parent_id = _fake_commit_id("ghost-parent") cid = _fake_commit_id("child") rec = _make_commit(cid, parent_commit_id=parent_id) with pytest.raises(MissingParentError, match="parent_commit_id"): write_commit(tmp_path, rec) def test_unknown_parent_produces_no_file(self, tmp_path: pathlib.Path) -> None: """After a MissingParentError, the commit file must not exist on disk.""" parent_id = _fake_commit_id("ghost-parent2") cid = _fake_commit_id("child2") rec = _make_commit(cid, parent_commit_id=parent_id) with pytest.raises(MissingParentError): write_commit(tmp_path, rec) assert not commit_exists(tmp_path, cid), ( "commit file was written even though parent is missing" ) def test_known_parent_does_not_raise_missing_parent_error(self, tmp_path: pathlib.Path) -> None: """write_commit must NOT raise MissingParentError when parent exists.""" parent_id = _fake_commit_id("real-parent") _plant_commit_file(tmp_path, parent_id) cid = _fake_commit_id("valid-child") rec = _make_commit(cid, parent_commit_id=parent_id) with pytest.raises((ValueError, OSError)) as exc_info: write_commit(tmp_path, rec) assert not isinstance(exc_info.value, MissingParentError), ( f"Commit with existing parent raised MissingParentError: {exc_info.value}" ) # --------------------------------------------------------------------------- # Unit — missing parent2_commit_id (merge commits) → MissingParentError # --------------------------------------------------------------------------- class TestMissingParent2Rejected: def test_unknown_parent2_raises_missing_parent_error(self, tmp_path: pathlib.Path) -> None: parent_id = _fake_commit_id("p1-exists") _plant_commit_file(tmp_path, parent_id) parent2_id = _fake_commit_id("p2-ghost") cid = _fake_commit_id("merge-child") rec = _make_commit(cid, parent_commit_id=parent_id, parent2_commit_id=parent2_id) with pytest.raises(MissingParentError, match="parent2_commit_id"): write_commit(tmp_path, rec) def test_both_parents_known_does_not_raise_missing_parent_error(self, tmp_path: pathlib.Path) -> None: p1 = _fake_commit_id("p1-real") p2 = _fake_commit_id("p2-real") _plant_commit_file(tmp_path, p1) _plant_commit_file(tmp_path, p2) cid = _fake_commit_id("merge-ok") rec = _make_commit(cid, parent_commit_id=p1, parent2_commit_id=p2) with pytest.raises((ValueError, OSError)) as exc_info: write_commit(tmp_path, rec) assert not isinstance(exc_info.value, MissingParentError), ( f"Merge commit with both parents present raised MissingParentError: {exc_info.value}" ) # --------------------------------------------------------------------------- # Data integrity — commit_exists returns False after a rejected write # --------------------------------------------------------------------------- class TestDataIntegrity: def test_commit_id_not_in_store_after_missing_parent_reject(self, tmp_path: pathlib.Path) -> None: ghost = _fake_commit_id("ghost-data") cid = _fake_commit_id("orphaned") rec = _make_commit(cid, parent_commit_id=ghost) with pytest.raises(MissingParentError): write_commit(tmp_path, rec) assert not commit_exists(tmp_path, cid), ( "commit_exists returned True for a commit whose parent was missing" ) def test_missing_parent_error_is_value_error_subclass(self, tmp_path: pathlib.Path) -> None: """MissingParentError is a ValueError — callers using broad ValueError catches still work.""" ghost = _fake_commit_id("ghost-ve") cid = _fake_commit_id("child-ve") rec = _make_commit(cid, parent_commit_id=ghost) with pytest.raises(ValueError): write_commit(tmp_path, rec)