"""Tests for push object integrity guard. When a snapshot manifest references an object ID that exists in the manifest ("in DB") but the actual object file is missing from the local object store ("missing from storage"), the push must abort with a ValueError rather than proceeding and emitting ok=True. Guard location: muse/cli/commands/push.py::_push_stream — the loop over all_object_ids calls read_object; if it returns None and no promisor remote is configured, it raises ValueError. """ from __future__ import annotations import datetime import json import pathlib from unittest.mock import AsyncMock, MagicMock import pytest from muse._version import __version__ from muse.core.object_store import write_object from muse.core.pack import PushResult, RemoteInfo from muse.core.snapshot import compute_commit_id, compute_snapshot_id from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot from muse.core.types import blob_id from muse.core.paths import heads_dir, muse_dir _REPO_ID = "test-repo" _REMOTE_URL = "https://hub.example.com/repos/test-repo" # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _bare_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: dot_muse = muse_dir(tmp_path) for d in ("commits", "snapshots", "objects", "refs/heads", "remotes"): (dot_muse / d).mkdir(parents=True, exist_ok=True) (dot_muse / "HEAD").write_text("ref: refs/heads/main\n") (dot_muse / "repo.json").write_text( json.dumps({"repo_id": _REPO_ID, "schema_version": __version__, "domain": "code"}) ) # No config.toml / no remotes — ensures no promisor remote is present so # missing objects trigger the ValueError guard rather than being skipped. monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path)) monkeypatch.chdir(tmp_path) return tmp_path def _make_commit( root: pathlib.Path, label: str, parent_id: str | None = None, *, write_objects: bool = True, ) -> CommitRecord: raw = f"content-{label}".encode() oid = blob_id(raw) if write_objects: write_object(root, oid, raw) manifest = {"file.txt": oid} snap_id = compute_snapshot_id(manifest) snap = SnapshotRecord(snapshot_id=snap_id, manifest=manifest) write_snapshot(root, snap) committed_at = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc) parent_ids = [parent_id] if parent_id else [] cid = compute_commit_id( parent_ids=parent_ids, snapshot_id=snap_id, message=f"commit {label}", committed_at_iso=committed_at.isoformat(), ) commit = CommitRecord( repo_id=_REPO_ID, commit_id=cid, branch="main", snapshot_id=snap_id, message=f"commit {label}", committed_at=committed_at, parent_commit_id=parent_id, ) write_commit(root, commit) return commit def _mock_transport(result_head: str) -> MagicMock: transport = MagicMock() transport.fetch_remote_info.return_value = RemoteInfo( domain="code", default_branch="main", branch_heads={}, ) transport.push_stream_coro = AsyncMock(return_value=PushResult( ok=True, message="ok", branch_heads={"main": result_head}, )) return transport # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- def test_i2_external_ref_in_db_but_missing_from_storage_is_rejected( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: """Push must abort when a snapshot manifest references an object that is missing from the local object store — even though the object ID exists in the manifest (i.e. it is 'in DB' but 'missing from storage'). The guard in _push_stream raises ValueError rather than sending the push and letting the server discover the missing object. """ from muse.cli.commands.push import _push_stream root = _bare_repo(tmp_path, monkeypatch) # Write the commit and snapshot but deliberately skip writing the object # file — so the manifest has the oid but storage has no corresponding file. commit = _make_commit(root, "alpha", write_objects=False) (heads_dir(root) / "main").write_text(commit.commit_id) transport = _mock_transport(result_head=commit.commit_id) with pytest.raises(ValueError, match="missing from the local store"): _push_stream( transport, _REMOTE_URL, None, root, commit.commit_id, [], "main", False, )