"""TDD — Phase 4: fetch_mpack wired into clone and pull commands (issue #47). All clone and pull paths must call transport.fetch_mpack exclusively. Tests: CW0 clone (depth=None) calls fetch_mpack. CW1 clone --depth 1 also calls fetch_mpack. CW2 pull calls fetch_mpack. """ from __future__ import annotations import argparse import contextlib import pathlib from typing import TYPE_CHECKING, Callable from unittest.mock import MagicMock, patch import pytest from muse.core.types import blob_id, fake_id if TYPE_CHECKING: from muse.core.transport import SigningIdentity _FetchResult = dict[str, str | list[str] | int] # transport fetch response _KwVal = str | int | bool | None # ── helpers ─────────────────────────────────────────────────────────────────── _COMMIT_ID = fake_id("commit-1") _SNAP_ID = fake_id("snap-1") _REPO_ID = fake_id("repo-1") _FETCH_RESULT = { "repo_id": _REPO_ID, "domain": "code", "default_branch": "main", "branch_heads": {"main": _COMMIT_ID}, "commits": [], "snapshots": [], "blobs_received": 0, "shallow_commits": [], } _APPLY_RESULT = { "commits_written": 1, "snapshots_written": 1, "blobs_written": 0, "blobs_skipped": 0, } _REMOTE_INFO = { "repo_id": _REPO_ID, "domain": "code", "default_branch": "main", "branch_heads": {"main": _COMMIT_ID}, "object_count": 1, "size_bytes": 100, } def _make_transport() -> MagicMock: """Transport mock: fetch_mpack returns _FETCH_RESULT.""" t = MagicMock() t.fetch_remote_info.return_value = _REMOTE_INFO t.fetch_mpack.return_value = _FETCH_RESULT return t def _clone_patches(t: MagicMock) -> list[contextlib.AbstractContextManager[MagicMock]]: return [ patch("muse.cli.commands.clone.get_signing_identity", return_value=None), patch("muse.cli.commands.clone.make_transport", return_value=t), patch("muse.cli.commands.clone.apply_mpack", return_value=_APPLY_RESULT), patch("muse.cli.commands.clone.set_remote"), patch("muse.cli.commands.clone.set_remote_head"), patch("muse.cli.commands.clone.set_upstream"), patch("muse.cli.commands.clone.apply_manifest"), patch("muse.cli.commands.clone.read_commit", return_value=MagicMock(snapshot_id=_SNAP_ID)), patch("muse.cli.commands.clone.read_snapshot", return_value=MagicMock(manifest={})), ] # ══════════════════════════════════════════════════════════════════════════════ # CW0 — clone (depth=None) calls fetch_mpack # ══════════════════════════════════════════════════════════════════════════════ def test_cw0_clone_full_calls_fetch_mpack(tmp_path: pathlib.Path) -> None: """Full clone (no --depth) must call transport.fetch_mpack, not fetch_presign_or_stream.""" from muse.cli.commands.clone import run t = _make_transport() args = argparse.Namespace( url="https://hub.example.com/gabriel/repo", directory=str(tmp_path / "cloned"), branch=None, depth=None, dry_run=False, no_checkout=False, json_out=False, ) patches = _clone_patches(t) with patches[0], patches[1], patches[2], patches[3], patches[4], patches[5], patches[6], patches[7], patches[8]: run(args) t.fetch_mpack.assert_called_once() # ══════════════════════════════════════════════════════════════════════════════ # CW1 — clone --depth 1 calls fetch_mpack (presign-only policy) # ══════════════════════════════════════════════════════════════════════════════ def test_cw1_clone_shallow_calls_fetch_mpack(tmp_path: pathlib.Path) -> None: """--depth clone must call transport.fetch_mpack — presign-only policy, no stream path.""" from muse.cli.commands.clone import run t = _make_transport() args = argparse.Namespace( url="https://hub.example.com/gabriel/repo", directory=str(tmp_path / "cloned"), branch=None, depth=1, dry_run=False, no_checkout=False, json_out=False, ) patches = _clone_patches(t) with patches[0], patches[1], patches[2], patches[3], patches[4], patches[5], patches[6], patches[7], patches[8]: run(args) t.fetch_mpack.assert_called_once() # ══════════════════════════════════════════════════════════════════════════════ # CW2 — pull calls fetch_mpack # ══════════════════════════════════════════════════════════════════════════════ def test_cw2_pull_calls_fetch_mpack(tmp_path: pathlib.Path) -> None: """pull must call transport.fetch_mpack, not fetch_presign_or_stream.""" import argparse from muse.cli.commands.pull import run def _fetch_mpack( url: str, signing: "SigningIdentity | None", *, want: str, have: list[str], on_object: Callable[[str, bytes], None] | None = None, **kw: _KwVal, ) -> _FetchResult: return _FETCH_RESULT t = MagicMock() t.fetch_remote_info.return_value = _REMOTE_INFO t.fetch_mpack.side_effect = _fetch_mpack args = argparse.Namespace( remote="origin", branch_flag=None, branch_pos=None, no_merge=False, ff_only=False, dry_run=False, message=None, json_out=False, ) with ( patch("muse.cli.commands.pull.require_repo", return_value=tmp_path), patch("muse.cli.commands.pull.get_remote", return_value="https://hub.example.com"), patch("muse.cli.commands.pull.get_signing_identity", return_value=None), patch("muse.cli.commands.pull.read_current_branch", return_value="main"), patch("muse.cli.commands.pull.get_upstream", return_value=None), patch("muse.cli.commands.pull.make_transport", return_value=t), patch("muse.cli.commands.pull.get_remote_head", return_value=None), # not up-to-date patch("muse.cli.commands.pull.get_head_commit_id", return_value=None), patch("muse.cli.commands.pull.get_all_commits", return_value=[]), patch("muse.cli.commands.pull.apply_mpack", return_value=_APPLY_RESULT), patch("muse.cli.commands.pull.set_remote_head"), patch("muse.cli.commands.pull.apply_manifest"), patch("muse.cli.commands.pull.read_commit", return_value=MagicMock(snapshot_id=_SNAP_ID)), patch("muse.cli.commands.pull.read_snapshot", return_value=MagicMock(manifest={})), ): try: run(args) except SystemExit: pass # exit after pull is OK — we only care which transport method was called t.fetch_mpack.assert_called_once()