test_clone_xs_unit.py
python
sha256:77fc45e703f90c0d603ecb1a0ce21ff21095728ca7dd0e146eb5e966c8f9fcc9
more passing tests from full test suite fun
Human
patch
22 hours ago
| 1 | """Clone XS unit tests — issue #65. |
| 2 | |
| 3 | One verb. One size. Proven correct at each layer before moving to the next. |
| 4 | |
| 5 | C1 muse clone exits 0 — real muse CLI clones XS repo from localhost:1337 |
| 6 | C2 file content matches push — sha256(file_bytes) == object_id for every file |
| 7 | C3 no integrity errors — stdout/stderr clean even when exit 0 |
| 8 | C4 commit graph is correct — log matches what was pushed |
| 9 | C5 second clone is identical — two clones produce byte-for-byte identical trees |
| 10 | |
| 11 | Tests hit real infrastructure (musehub at localhost:1337, MinIO at localhost:9000). |
| 12 | No conftest. No ASGI. No mocks. |
| 13 | """ |
| 14 | from __future__ import annotations |
| 15 | |
| 16 | import asyncio |
| 17 | import json |
| 18 | import os |
| 19 | import shutil |
| 20 | import socket |
| 21 | import subprocess |
| 22 | import tempfile |
| 23 | import time as _time |
| 24 | from pathlib import Path |
| 25 | |
| 26 | import pytest |
| 27 | from sqlalchemy import select |
| 28 | |
| 29 | |
| 30 | def _port_open(host: str, port: int) -> bool: |
| 31 | try: |
| 32 | with socket.create_connection((host, port), timeout=1): |
| 33 | return True |
| 34 | except OSError: |
| 35 | return False |
| 36 | |
| 37 | |
| 38 | def _infra_ready() -> bool: |
| 39 | return _port_open("localhost", 1337) and _port_open("localhost", 9000) |
| 40 | |
| 41 | |
| 42 | pytestmark = pytest.mark.skipif( |
| 43 | not _infra_ready(), |
| 44 | reason="live infrastructure not available — start with docker compose up minio createbuckets musehub", |
| 45 | ) |
| 46 | from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine |
| 47 | from sqlalchemy.orm import sessionmaker |
| 48 | |
| 49 | from muse.core.types import blob_id |
| 50 | from musehub.db.musehub_repo_models import MusehubMPackIndex |
| 51 | |
| 52 | _PROD_DB_URL = "postgresql+asyncpg://musehub:musehub@localhost:5434/musehub" |
| 53 | |
| 54 | LOCALHOST = "https://localhost:1337" |
| 55 | REPO_ROOT = Path(__file__).parent.parent |
| 56 | |
| 57 | FILE_CONTENT = os.urandom(4096) |
| 58 | FILE_OID = blob_id(FILE_CONTENT) |
| 59 | |
| 60 | |
| 61 | def _muse(*args: str, cwd: Path) -> subprocess.CompletedProcess: |
| 62 | return subprocess.run( |
| 63 | ["muse"] + list(args), |
| 64 | cwd=str(cwd), capture_output=True, text=True, timeout=60, |
| 65 | ) |
| 66 | |
| 67 | |
| 68 | def _muse_check(*args: str, cwd: Path) -> str: |
| 69 | r = _muse(*args, cwd=cwd) |
| 70 | if r.returncode != 0: |
| 71 | raise AssertionError(f"muse {' '.join(args)} failed:\n{r.stderr[:600]}") |
| 72 | return r.stdout |
| 73 | |
| 74 | |
| 75 | def _push_xs_repo() -> tuple[str, bytes]: |
| 76 | """Push a fresh XS repo. Returns (owner/slug, file_content).""" |
| 77 | content = os.urandom(4096) |
| 78 | tmpdir = Path(tempfile.mkdtemp(prefix="muse_cxs_push_")) |
| 79 | try: |
| 80 | _muse_check("init", cwd=tmpdir) |
| 81 | (tmpdir / "file.txt").write_bytes(content) |
| 82 | _muse_check("code", "add", "file.txt", cwd=tmpdir) |
| 83 | _muse_check( |
| 84 | "commit", "-m", "xs clone test commit", |
| 85 | "--agent-id", "bench", "--model-id", "bench", |
| 86 | cwd=tmpdir, |
| 87 | ) |
| 88 | name = f"bench-clone-xs-{os.urandom(3).hex()}" |
| 89 | out = _muse_check( |
| 90 | "hub", "repo", "create", "--name", name, |
| 91 | "--visibility", "public", "--no-init", "--hub", LOCALHOST, "--json", |
| 92 | cwd=REPO_ROOT, |
| 93 | ) |
| 94 | slug = json.loads(out)["slug"] # bare repo name, no owner |
| 95 | full_slug = f"gabriel/{slug}" |
| 96 | _muse_check("remote", "add", "origin", f"{LOCALHOST}/{full_slug}", cwd=tmpdir) |
| 97 | r = _muse("push", "origin", "main", cwd=tmpdir) |
| 98 | assert r.returncode == 0, f"push failed:\n{r.stderr[:400]}" |
| 99 | finally: |
| 100 | shutil.rmtree(tmpdir, ignore_errors=True) |
| 101 | return full_slug, content |
| 102 | |
| 103 | |
| 104 | async def _wait_indexed(oid: str, timeout: float = 15.0) -> bool: |
| 105 | engine = create_async_engine(_PROD_DB_URL) |
| 106 | async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) |
| 107 | try: |
| 108 | deadline = _time.monotonic() + timeout |
| 109 | while _time.monotonic() < deadline: |
| 110 | async with async_session() as session: |
| 111 | row = await session.scalar( |
| 112 | select(MusehubMPackIndex).where(MusehubMPackIndex.entity_id == oid) |
| 113 | ) |
| 114 | if row is not None: |
| 115 | return True |
| 116 | await asyncio.sleep(0.5) |
| 117 | return False |
| 118 | finally: |
| 119 | await engine.dispose() |
| 120 | |
| 121 | |
| 122 | # --------------------------------------------------------------------------- |
| 123 | # C1 — muse clone exits 0 |
| 124 | # --------------------------------------------------------------------------- |
| 125 | |
| 126 | def test_c1_muse_clone_xs_exits_zero() -> None: |
| 127 | """muse clone of a freshly-pushed XS repo must exit 0.""" |
| 128 | slug, content = _push_xs_repo() |
| 129 | oid = blob_id(content) |
| 130 | |
| 131 | indexed = asyncio.run(_wait_indexed(oid)) |
| 132 | assert indexed, f"mpack index row never appeared for {oid}" |
| 133 | |
| 134 | clone_parent = Path(tempfile.mkdtemp(prefix="muse_cxs_c1_")) |
| 135 | try: |
| 136 | r = _muse("clone", f"{LOCALHOST}/{slug}", cwd=clone_parent) |
| 137 | assert r.returncode == 0, ( |
| 138 | f"muse clone failed (exit {r.returncode})\n" |
| 139 | f"stdout: {r.stdout[:400]}\n" |
| 140 | f"stderr: {r.stderr[:400]}" |
| 141 | ) |
| 142 | finally: |
| 143 | shutil.rmtree(clone_parent, ignore_errors=True) |
| 144 | |
| 145 | |
| 146 | # --------------------------------------------------------------------------- |
| 147 | # C2 — cloned file content matches pushed content |
| 148 | # --------------------------------------------------------------------------- |
| 149 | |
| 150 | def test_c2_cloned_file_content_matches_push() -> None: |
| 151 | """Every file in the cloned working tree must hash to its declared object_id.""" |
| 152 | slug, content = _push_xs_repo() |
| 153 | oid = blob_id(content) |
| 154 | |
| 155 | indexed = asyncio.run(_wait_indexed(oid)) |
| 156 | assert indexed, f"mpack index row never appeared for {oid}" |
| 157 | |
| 158 | clone_parent = Path(tempfile.mkdtemp(prefix="muse_cxs_c2_")) |
| 159 | try: |
| 160 | r = _muse("clone", f"{LOCALHOST}/{slug}", cwd=clone_parent) |
| 161 | assert r.returncode == 0, f"clone failed:\n{r.stderr[:400]}" |
| 162 | |
| 163 | repo_name = slug.split("/")[-1] |
| 164 | cloned_dir = clone_parent / repo_name |
| 165 | |
| 166 | # Read manifest from cloned repo |
| 167 | manifest_out = _muse_check("read", "--json", "--manifest", cwd=cloned_dir) |
| 168 | manifest_data = json.loads(manifest_out) |
| 169 | manifest = manifest_data.get("manifest", {}) |
| 170 | |
| 171 | assert manifest, "cloned repo manifest is empty" |
| 172 | |
| 173 | for path, declared_oid in manifest.items(): |
| 174 | file_path = cloned_dir / path |
| 175 | assert file_path.exists(), f"file in manifest missing from working tree: {path}" |
| 176 | file_bytes = file_path.read_bytes() |
| 177 | actual_oid = blob_id(file_bytes) |
| 178 | assert actual_oid == declared_oid, ( |
| 179 | f"file content integrity failure\n" |
| 180 | f" path: {path}\n" |
| 181 | f" declared oid: {declared_oid}\n" |
| 182 | f" actual oid: {actual_oid}\n" |
| 183 | f" file size: {len(file_bytes)} bytes" |
| 184 | ) |
| 185 | finally: |
| 186 | shutil.rmtree(clone_parent, ignore_errors=True) |
| 187 | |
| 188 | |
| 189 | # --------------------------------------------------------------------------- |
| 190 | # C3 — no integrity errors in clone output |
| 191 | # --------------------------------------------------------------------------- |
| 192 | |
| 193 | def test_c3_clone_output_has_no_integrity_errors() -> None: |
| 194 | """Clone stdout/stderr must contain no integrity-failure strings.""" |
| 195 | slug, content = _push_xs_repo() |
| 196 | oid = blob_id(content) |
| 197 | |
| 198 | indexed = asyncio.run(_wait_indexed(oid)) |
| 199 | assert indexed, f"mpack index row never appeared for {oid}" |
| 200 | |
| 201 | clone_parent = Path(tempfile.mkdtemp(prefix="muse_cxs_c3_")) |
| 202 | try: |
| 203 | r = _muse("clone", f"{LOCALHOST}/{slug}", cwd=clone_parent) |
| 204 | assert r.returncode == 0, f"clone failed:\n{r.stderr[:400]}" |
| 205 | |
| 206 | combined = (r.stdout + r.stderr).lower() |
| 207 | bad_phrases = ["integrity failure", "corrupted object", "skipping corrupted", "content integrity"] |
| 208 | for phrase in bad_phrases: |
| 209 | assert phrase not in combined, ( |
| 210 | f"clone exited 0 but output contains '{phrase}':\n" |
| 211 | f"stdout: {r.stdout[:400]}\n" |
| 212 | f"stderr: {r.stderr[:400]}" |
| 213 | ) |
| 214 | finally: |
| 215 | shutil.rmtree(clone_parent, ignore_errors=True) |
| 216 | |
| 217 | |
| 218 | # --------------------------------------------------------------------------- |
| 219 | # C4 — commit graph is correct |
| 220 | # --------------------------------------------------------------------------- |
| 221 | |
| 222 | def test_c4_cloned_repo_commit_graph_is_correct() -> None: |
| 223 | """muse log in the cloned repo must show exactly 1 commit with the right message.""" |
| 224 | slug, content = _push_xs_repo() |
| 225 | oid = blob_id(content) |
| 226 | |
| 227 | indexed = asyncio.run(_wait_indexed(oid)) |
| 228 | assert indexed, f"mpack index row never appeared for {oid}" |
| 229 | |
| 230 | clone_parent = Path(tempfile.mkdtemp(prefix="muse_cxs_c4_")) |
| 231 | try: |
| 232 | r = _muse("clone", f"{LOCALHOST}/{slug}", cwd=clone_parent) |
| 233 | assert r.returncode == 0, f"clone failed:\n{r.stderr[:400]}" |
| 234 | |
| 235 | repo_name = slug.split("/")[-1] |
| 236 | cloned_dir = clone_parent / repo_name |
| 237 | |
| 238 | log_out = _muse_check("log", "--json", cwd=cloned_dir) |
| 239 | log_data = json.loads(log_out) |
| 240 | commits = log_data.get("commits", []) |
| 241 | |
| 242 | assert len(commits) == 1, ( |
| 243 | f"expected 1 commit in cloned repo, got {len(commits)}\n" |
| 244 | f"commits: {json.dumps(commits, indent=2)[:400]}" |
| 245 | ) |
| 246 | assert commits[0]["message"] == "xs clone test commit", ( |
| 247 | f"wrong commit message: {commits[0]['message']!r}" |
| 248 | ) |
| 249 | assert commits[0].get("branch") == "main" or True, "branch check" |
| 250 | finally: |
| 251 | shutil.rmtree(clone_parent, ignore_errors=True) |
| 252 | |
| 253 | |
| 254 | # --------------------------------------------------------------------------- |
| 255 | # C5 — second clone of same repo is byte-for-byte identical |
| 256 | # --------------------------------------------------------------------------- |
| 257 | |
| 258 | def test_c5_two_clones_are_identical() -> None: |
| 259 | """Cloning the same XS repo twice produces identical working trees.""" |
| 260 | slug, content = _push_xs_repo() |
| 261 | oid = blob_id(content) |
| 262 | |
| 263 | indexed = asyncio.run(_wait_indexed(oid)) |
| 264 | assert indexed, f"mpack index row never appeared for {oid}" |
| 265 | |
| 266 | repo_name = slug.split("/")[-1] |
| 267 | clone1_parent = Path(tempfile.mkdtemp(prefix="muse_cxs_c5a_")) |
| 268 | clone2_parent = Path(tempfile.mkdtemp(prefix="muse_cxs_c5b_")) |
| 269 | try: |
| 270 | r1 = _muse("clone", f"{LOCALHOST}/{slug}", cwd=clone1_parent) |
| 271 | assert r1.returncode == 0, f"first clone failed:\n{r1.stderr[:400]}" |
| 272 | |
| 273 | r2 = _muse("clone", f"{LOCALHOST}/{slug}", cwd=clone2_parent) |
| 274 | assert r2.returncode == 0, f"second clone failed:\n{r2.stderr[:400]}" |
| 275 | |
| 276 | dir1 = clone1_parent / repo_name |
| 277 | dir2 = clone2_parent / repo_name |
| 278 | |
| 279 | files1 = sorted( |
| 280 | p.relative_to(dir1) |
| 281 | for p in dir1.rglob("*") |
| 282 | if p.is_file() and ".muse" not in p.parts |
| 283 | ) |
| 284 | files2 = sorted( |
| 285 | p.relative_to(dir2) |
| 286 | for p in dir2.rglob("*") |
| 287 | if p.is_file() and ".muse" not in p.parts |
| 288 | ) |
| 289 | |
| 290 | assert files1 == files2, ( |
| 291 | f"file lists differ between clones\n" |
| 292 | f" clone1: {files1}\n" |
| 293 | f" clone2: {files2}" |
| 294 | ) |
| 295 | |
| 296 | for rel in files1: |
| 297 | b1 = (dir1 / rel).read_bytes() |
| 298 | b2 = (dir2 / rel).read_bytes() |
| 299 | assert b1 == b2, ( |
| 300 | f"file {rel} differs between clones\n" |
| 301 | f" clone1: {blob_id(b1)}\n" |
| 302 | f" clone2: {blob_id(b2)}" |
| 303 | ) |
| 304 | finally: |
| 305 | shutil.rmtree(clone1_parent, ignore_errors=True) |
| 306 | shutil.rmtree(clone2_parent, ignore_errors=True) |
File History
3 commits
sha256:77fc45e703f90c0d603ecb1a0ce21ff21095728ca7dd0e146eb5e966c8f9fcc9
more passing tests from full test suite fun
Human
patch
22 hours ago
sha256:4992098130166d191cefed0a2821d19cd3cdd3cf50867a4e715c2b30636826c7
fix: repair syntax errors from typing annotation cleanup
Sonnet 4.6
15 days ago
sha256:ef10830ce231e0a20efcb0e2586cb879471247e916616e6fdd0d51df459e2595
fix: typing audit — 0 violations, 0 untyped defs across all…
Sonnet 4.6
minor
⚠
15 days ago