test_push_object_integrity.py
python
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d
feat(pack): delta-encode snapshots in MPackBundle wire format
Sonnet 4.6
minor
⚠ breaking
121 days ago
| 1 | """Tests for push object integrity guard. |
| 2 | |
| 3 | When a snapshot manifest references an object ID that exists in the manifest |
| 4 | ("in DB") but the actual object file is missing from the local object store |
| 5 | ("missing from storage"), the push must abort with a ValueError rather than |
| 6 | proceeding and emitting ok=True. |
| 7 | |
| 8 | Guard location: muse/cli/commands/push.py::_push_stream — the loop over |
| 9 | all_object_ids calls read_object; if it returns None and no promisor remote |
| 10 | is configured, it raises ValueError. |
| 11 | """ |
| 12 | from __future__ import annotations |
| 13 | |
| 14 | import datetime |
| 15 | import json |
| 16 | import pathlib |
| 17 | from unittest.mock import AsyncMock, MagicMock |
| 18 | |
| 19 | import pytest |
| 20 | |
| 21 | from muse._version import __version__ |
| 22 | from muse.core.object_store import write_object |
| 23 | from muse.core.pack import PushResult, RemoteInfo |
| 24 | from muse.core.snapshot import compute_commit_id, compute_snapshot_id |
| 25 | from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot |
| 26 | from muse.core.types import blob_id |
| 27 | from muse.core.paths import heads_dir, muse_dir |
| 28 | |
| 29 | _REPO_ID = "test-repo" |
| 30 | _REMOTE_URL = "https://hub.example.com/repos/test-repo" |
| 31 | |
| 32 | |
| 33 | # --------------------------------------------------------------------------- |
| 34 | # Helpers |
| 35 | # --------------------------------------------------------------------------- |
| 36 | |
| 37 | |
| 38 | def _bare_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: |
| 39 | dot_muse = muse_dir(tmp_path) |
| 40 | for d in ("commits", "snapshots", "objects", "refs/heads", "remotes"): |
| 41 | (dot_muse / d).mkdir(parents=True, exist_ok=True) |
| 42 | (dot_muse / "HEAD").write_text("ref: refs/heads/main\n") |
| 43 | (dot_muse / "repo.json").write_text( |
| 44 | json.dumps({"repo_id": _REPO_ID, "schema_version": __version__, "domain": "code"}) |
| 45 | ) |
| 46 | # No config.toml / no remotes — ensures no promisor remote is present so |
| 47 | # missing objects trigger the ValueError guard rather than being skipped. |
| 48 | monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path)) |
| 49 | monkeypatch.chdir(tmp_path) |
| 50 | return tmp_path |
| 51 | |
| 52 | |
| 53 | def _make_commit( |
| 54 | root: pathlib.Path, |
| 55 | label: str, |
| 56 | parent_id: str | None = None, |
| 57 | *, |
| 58 | write_objects: bool = True, |
| 59 | ) -> CommitRecord: |
| 60 | raw = f"content-{label}".encode() |
| 61 | oid = blob_id(raw) |
| 62 | if write_objects: |
| 63 | write_object(root, oid, raw) |
| 64 | manifest = {"file.txt": oid} |
| 65 | snap_id = compute_snapshot_id(manifest) |
| 66 | snap = SnapshotRecord(snapshot_id=snap_id, manifest=manifest) |
| 67 | write_snapshot(root, snap) |
| 68 | committed_at = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc) |
| 69 | parent_ids = [parent_id] if parent_id else [] |
| 70 | cid = compute_commit_id( |
| 71 | parent_ids=parent_ids, |
| 72 | snapshot_id=snap_id, |
| 73 | message=f"commit {label}", |
| 74 | committed_at_iso=committed_at.isoformat(), |
| 75 | ) |
| 76 | commit = CommitRecord( |
| 77 | repo_id=_REPO_ID, |
| 78 | commit_id=cid, |
| 79 | branch="main", |
| 80 | snapshot_id=snap_id, |
| 81 | message=f"commit {label}", |
| 82 | committed_at=committed_at, |
| 83 | parent_commit_id=parent_id, |
| 84 | ) |
| 85 | write_commit(root, commit) |
| 86 | return commit |
| 87 | |
| 88 | |
| 89 | def _mock_transport(result_head: str) -> MagicMock: |
| 90 | transport = MagicMock() |
| 91 | transport.fetch_remote_info.return_value = RemoteInfo( |
| 92 | domain="code", |
| 93 | default_branch="main", |
| 94 | branch_heads={}, |
| 95 | ) |
| 96 | transport.push_stream_coro = AsyncMock(return_value=PushResult( |
| 97 | ok=True, |
| 98 | message="ok", |
| 99 | branch_heads={"main": result_head}, |
| 100 | )) |
| 101 | return transport |
| 102 | |
| 103 | |
| 104 | # --------------------------------------------------------------------------- |
| 105 | # Tests |
| 106 | # --------------------------------------------------------------------------- |
| 107 | |
| 108 | |
| 109 | def test_i2_external_ref_in_db_but_missing_from_storage_is_rejected( |
| 110 | tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch |
| 111 | ) -> None: |
| 112 | """Push must abort when a snapshot manifest references an object that is |
| 113 | missing from the local object store — even though the object ID exists in |
| 114 | the manifest (i.e. it is 'in DB' but 'missing from storage'). |
| 115 | |
| 116 | The guard in _push_stream raises ValueError rather than sending the push |
| 117 | and letting the server discover the missing object. |
| 118 | """ |
| 119 | from muse.cli.commands.push import _push_stream |
| 120 | |
| 121 | root = _bare_repo(tmp_path, monkeypatch) |
| 122 | |
| 123 | # Write the commit and snapshot but deliberately skip writing the object |
| 124 | # file — so the manifest has the oid but storage has no corresponding file. |
| 125 | commit = _make_commit(root, "alpha", write_objects=False) |
| 126 | (heads_dir(root) / "main").write_text(commit.commit_id) |
| 127 | |
| 128 | transport = _mock_transport(result_head=commit.commit_id) |
| 129 | |
| 130 | with pytest.raises(ValueError, match="missing from the local store"): |
| 131 | _push_stream( |
| 132 | transport, |
| 133 | _REMOTE_URL, |
| 134 | None, |
| 135 | root, |
| 136 | commit.commit_id, |
| 137 | [], |
| 138 | "main", |
| 139 | False, |
| 140 | ) |
File History
1 commit
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d
feat(pack): delta-encode snapshots in MPackBundle wire format
Sonnet 4.6
minor
⚠
121 days ago