"""TDD tests for muse.core.pack_store — MPack local object store. Phase 1 of issue #70: MPack unified binary format. Layout: .muse/objects/pack/sha256/<64hex>.mpack ← pack data .muse/objects/pack/sha256/<64hex>.idx ← seek index """ from __future__ import annotations import hashlib import json import pathlib import struct import pytest from muse.core.paths import muse_dir, packs_dir from muse.core.types import blob_id # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture def repo(tmp_path: pathlib.Path) -> pathlib.Path: """Minimal .muse/ repo structure.""" dot_muse = muse_dir(tmp_path) (dot_muse / "commits").mkdir(parents=True) (dot_muse / "snapshots").mkdir(parents=True) (dot_muse / "objects").mkdir(parents=True) (dot_muse / "refs" / "heads").mkdir(parents=True) (dot_muse / "repo.json").write_text(json.dumps({"repo_id": "test-repo"})) (dot_muse / "HEAD").write_text("ref: refs/heads/main\n") (dot_muse / "refs" / "heads" / "main").write_text("") return tmp_path def _make_objects(n: int, prefix: str = "obj") -> list[tuple[str, bytes]]: """Return n (object_id, content) pairs with deterministic content.""" objects = [] for i in range(n): content = f"{prefix}-{i}".encode() * 16 oid = blob_id(content) objects.append((oid, content)) return objects # --------------------------------------------------------------------------- # Path helpers # --------------------------------------------------------------------------- class TestPacksDir: def test_packs_dir_is_under_objects(self, repo: pathlib.Path) -> None: from muse.core.paths import objects_dir p = packs_dir(repo) assert str(p).startswith(str(objects_dir(repo))) def test_packs_dir_algo_subdir_is_sha256(self, repo: pathlib.Path) -> None: p = packs_dir(repo) assert p.name == "sha256" assert p.parent.name == "pack" # --------------------------------------------------------------------------- # write_pack # --------------------------------------------------------------------------- class TestWritePack: def test_write_pack_creates_mpack_and_idx(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack objects = _make_objects(3) pack_id = write_pack(repo, objects) _, hex_id = pack_id.split(":") base = packs_dir(repo) / hex_id assert base.with_suffix(".mpack").exists() assert base.with_suffix(".idx").exists() def test_pack_path_contains_sha256_algo_dir(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack objects = _make_objects(2) pack_id = write_pack(repo, objects) _, hex_id = pack_id.split(":") mpack_path = packs_dir(repo) / f"{hex_id}.mpack" assert "pack/sha256" in str(mpack_path) def test_pack_id_is_sha256_of_pack_content(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack objects = _make_objects(3) pack_id = write_pack(repo, objects) _, hex_id = pack_id.split(":") mpack_path = packs_dir(repo) / f"{hex_id}.mpack" actual_hex = hashlib.sha256(mpack_path.read_bytes()).hexdigest() assert actual_hex == hex_id def test_pack_file_starts_with_muse_magic(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack objects = _make_objects(2) pack_id = write_pack(repo, objects) _, hex_id = pack_id.split(":") mpack_path = packs_dir(repo) / f"{hex_id}.mpack" assert mpack_path.read_bytes()[:4] == b"MUSE" def test_idx_file_starts_with_musi_magic(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack objects = _make_objects(2) pack_id = write_pack(repo, objects) _, hex_id = pack_id.split(":") idx_path = packs_dir(repo) / f"{hex_id}.idx" assert idx_path.read_bytes()[:4] == b"MUSI" def test_pack_file_encodes_object_count(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack objects = _make_objects(7) pack_id = write_pack(repo, objects) _, hex_id = pack_id.split(":") mpack_path = packs_dir(repo) / f"{hex_id}.mpack" data = mpack_path.read_bytes() # magic(4) + version(1) + object_count(8) count = struct.unpack_from(" None: from muse.core.pack_store import write_pack result = write_pack(repo, []) assert result is None assert not any(packs_dir(repo).rglob("*.mpack")) if packs_dir(repo).exists() else True def test_write_pack_returns_prefixed_pack_id(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack objects = _make_objects(1) pack_id = write_pack(repo, objects) assert pack_id.startswith("sha256:") assert len(pack_id) == 7 + 64 def test_write_pack_idempotent(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack objects = _make_objects(3) pack_id_1 = write_pack(repo, objects) pack_id_2 = write_pack(repo, objects) assert pack_id_1 == pack_id_2 # Only one .mpack file should exist mpack_files = list(packs_dir(repo).glob("*.mpack")) assert len(mpack_files) == 1 # --------------------------------------------------------------------------- # read_object_from_packs # --------------------------------------------------------------------------- class TestReadObjectFromPacks: def test_returns_correct_bytes(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, read_object_from_packs content = b"hello mpack world" oid = blob_id(content) write_pack(repo, [(oid, content)]) assert read_object_from_packs(repo, oid) == content def test_returns_none_for_missing(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, read_object_from_packs write_pack(repo, _make_objects(2)) missing = blob_id(b"not in any pack") assert read_object_from_packs(repo, missing) is None def test_returns_none_when_no_packs_exist(self, repo: pathlib.Path) -> None: from muse.core.pack_store import read_object_from_packs oid = blob_id(b"anything") assert read_object_from_packs(repo, oid) is None def test_byte_exact_round_trip(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, read_object_from_packs objects = _make_objects(10) write_pack(repo, objects) for oid, content in objects: assert read_object_from_packs(repo, oid) == content def test_binary_search_finds_object_in_large_pack(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, read_object_from_packs objects = _make_objects(500) write_pack(repo, objects) # Pick objects from start, middle, and end of sorted order sorted_objects = sorted(objects, key=lambda x: x[0]) for oid, content in [sorted_objects[0], sorted_objects[250], sorted_objects[-1]]: assert read_object_from_packs(repo, oid) == content def test_reads_across_multiple_packs(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, read_object_from_packs pack1 = _make_objects(3, prefix="pack1") pack2 = _make_objects(3, prefix="pack2") pack3 = _make_objects(3, prefix="pack3") write_pack(repo, pack1) write_pack(repo, pack2) write_pack(repo, pack3) for oid, content in pack1 + pack2 + pack3: assert read_object_from_packs(repo, oid) == content # --------------------------------------------------------------------------- # has_object_in_packs # --------------------------------------------------------------------------- class TestHasObjectInPacks: def test_finds_written_object(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, has_object_in_packs content = b"find me" oid = blob_id(content) write_pack(repo, [(oid, content)]) assert has_object_in_packs(repo, oid) is True def test_returns_false_for_missing(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, has_object_in_packs write_pack(repo, _make_objects(2)) missing = blob_id(b"not here") assert has_object_in_packs(repo, missing) is False def test_returns_false_when_no_packs_exist(self, repo: pathlib.Path) -> None: from muse.core.pack_store import has_object_in_packs assert has_object_in_packs(repo, blob_id(b"x")) is False def test_finds_objects_across_multiple_packs(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, has_object_in_packs pack1 = _make_objects(3, prefix="a") pack2 = _make_objects(3, prefix="b") write_pack(repo, pack1) write_pack(repo, pack2) for oid, _ in pack1 + pack2: assert has_object_in_packs(repo, oid) is True # --------------------------------------------------------------------------- # list_packs # --------------------------------------------------------------------------- class TestListPacks: def test_empty_when_no_packs(self, repo: pathlib.Path) -> None: from muse.core.pack_store import list_packs assert list_packs(repo) == [] def test_returns_all_pack_ids(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, list_packs id1 = write_pack(repo, _make_objects(2, prefix="a")) id2 = write_pack(repo, _make_objects(2, prefix="b")) result = list_packs(repo) assert sorted(result) == sorted([id1, id2]) # --------------------------------------------------------------------------- # verify_pack # --------------------------------------------------------------------------- class TestVerifyPack: def test_passes_on_valid_pack(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, verify_pack pack_id = write_pack(repo, _make_objects(5)) assert verify_pack(repo, pack_id) is True def test_raises_on_corrupted_mpack(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, verify_pack pack_id = write_pack(repo, _make_objects(3)) _, hex_id = pack_id.split(":") mpack_path = packs_dir(repo) / f"{hex_id}.mpack" data = bytearray(mpack_path.read_bytes()) data[20] ^= 0xFF # flip a byte in the object data mpack_path.write_bytes(bytes(data)) with pytest.raises(OSError): verify_pack(repo, pack_id) def test_raises_on_corrupted_idx(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack, verify_pack pack_id = write_pack(repo, _make_objects(3)) _, hex_id = pack_id.split(":") idx_path = packs_dir(repo) / f"{hex_id}.idx" data = bytearray(idx_path.read_bytes()) data[20] ^= 0xFF idx_path.write_bytes(bytes(data)) with pytest.raises(OSError): verify_pack(repo, pack_id) # --------------------------------------------------------------------------- # object_store fallthrough # --------------------------------------------------------------------------- class TestObjectStoreFallthrough: def test_read_object_finds_pack_objects(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack from muse.core.object_store import read_object content = b"packed content" oid = blob_id(content) write_pack(repo, [(oid, content)]) assert read_object(repo, oid) == content def test_has_object_finds_pack_objects(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack from muse.core.object_store import has_object content = b"packed object" oid = blob_id(content) write_pack(repo, [(oid, content)]) assert has_object(repo, oid) is True def test_loose_object_takes_priority_over_pack(self, repo: pathlib.Path) -> None: from muse.core.pack_store import write_pack from muse.core.object_store import read_object, write_object content = b"real content" oid = blob_id(content) write_object(repo, oid, content) # Pack with same oid is irrelevant — loose wins write_pack(repo, [(oid, content)]) assert read_object(repo, oid) == content def test_read_object_returns_none_when_absent_everywhere(self, repo: pathlib.Path) -> None: from muse.core.object_store import read_object missing = blob_id(b"nowhere") assert read_object(repo, missing) is None