"""Tests for ``muse describe`` and ``muse/core/describe.py``. Covers: no tags fallback to SHA, tag at tip, tag behind tip (distance), --long format, --require-tag exit-1, --format json, core describe_commit, stress: deep ancestry. """ from __future__ import annotations import datetime import json import pathlib import pytest from tests.cli_test_helper import CliRunner cli = None # argparse migration — CliRunner ignores this arg from muse.core.describe import describe_commit from muse.core.object_store import write_object from muse.core.ids import hash_commit, hash_snapshot from muse.core.commits import ( CommitRecord, write_commit, ) from muse.core.snapshots import ( SnapshotRecord, write_snapshot, ) from muse.core.tags import ( TagRecord, write_tag, ) from muse.core.types import Manifest, blob_id runner = CliRunner() from muse.core.types import content_hash as _content_hash from muse.core.paths import muse_dir, ref_path _REPO_ID = _content_hash({"name": "describe-test"}) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _init_repo(path: pathlib.Path) -> pathlib.Path: dot_muse = muse_dir(path) for d in ("commits", "snapshots", "objects", "refs/heads", "tags"): (dot_muse / d).mkdir(parents=True, exist_ok=True) (dot_muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8") (dot_muse / "repo.json").write_text( json.dumps({"repo_id": _REPO_ID, "domain": "midi"}), encoding="utf-8" ) return path def _env(repo: pathlib.Path) -> Manifest: return {"MUSE_REPO_ROOT": str(repo)} def _make_commit( root: pathlib.Path, parent_id: str | None = None, content: bytes = b"data", branch: str = "main", ) -> str: obj_id = blob_id(content) write_object(root, obj_id, content) manifest = {f"file_{obj_id[7:15]}.txt": obj_id} snap_id = hash_snapshot(manifest) write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest)) committed_at = datetime.datetime.now(datetime.timezone.utc) parent_ids = [parent_id] if parent_id else [] commit_id = hash_commit( parent_ids=parent_ids, snapshot_id=snap_id, message=f"commit on {branch}", committed_at_iso=committed_at.isoformat(), ) write_commit(root, CommitRecord( commit_id=commit_id, branch=branch, snapshot_id=snap_id, message=f"commit on {branch}", committed_at=committed_at, parent_commit_id=parent_id, )) (ref_path(root, branch)).write_text(commit_id, encoding="utf-8") return commit_id def _make_tag(root: pathlib.Path, tag: str, commit_id: str) -> None: write_tag(root, TagRecord( tag_id=_content_hash({"tag": tag, "commit_id": commit_id}), repo_id=_REPO_ID, tag=tag, commit_id=commit_id, created_at=datetime.datetime.now(datetime.timezone.utc), )) # --------------------------------------------------------------------------- # Unit: core describe_commit # --------------------------------------------------------------------------- def test_describe_no_tags_returns_shortblob_id(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"alpha") result = describe_commit(tmp_path, _REPO_ID, cid) assert result["tag"] is None assert result["short_sha"] == cid[:len("sha256:") + 12] assert result["name"] == result["short_sha"] def test_describe_tag_at_tip(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"beta") _make_tag(tmp_path, "v1.0.0", cid) result = describe_commit(tmp_path, _REPO_ID, cid) assert result["tag"] == "v1.0.0" assert result["distance"] == 0 assert result["name"] == "v1.0.0" def test_describe_tag_one_hop_behind(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid1 = _make_commit(tmp_path, content=b"first") _make_tag(tmp_path, "v0.9.0", cid1) cid2 = _make_commit(tmp_path, parent_id=cid1, content=b"second") result = describe_commit(tmp_path, _REPO_ID, cid2) assert result["tag"] == "v0.9.0" assert result["distance"] == 1 assert result["name"] == "v0.9.0~1" def test_describe_long_format(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"gamma") _make_tag(tmp_path, "v2.0.0", cid) result = describe_commit(tmp_path, _REPO_ID, cid, long_format=True) assert result["tag"] == "v2.0.0" assert result["distance"] == 0 # Long format always includes distance + short_sha (no git-style 'g' prefix). assert result["name"].startswith("v2.0.0-0-sha256:") # --------------------------------------------------------------------------- # CLI: muse describe # --------------------------------------------------------------------------- def test_describe_cli_help() -> None: result = runner.invoke(cli, ["describe", "--help"]) assert result.exit_code == 0 assert "--long" in result.output or "-l" in result.output def test_describe_cli_no_commits(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) result = runner.invoke(cli, ["describe"], env=_env(tmp_path)) assert result.exit_code != 0 def test_describe_cli_text_output(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"cli-test") _make_tag(tmp_path, "v3.0.0", cid) result = runner.invoke(cli, ["describe"], env=_env(tmp_path)) assert result.exit_code == 0 assert "v3.0.0" in result.output def test_describe_cli_json_output(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"json-test") _make_tag(tmp_path, "v4.0.0", cid) result = runner.invoke(cli, ["describe", "--json"], env=_env(tmp_path)) assert result.exit_code == 0 data = json.loads(result.output) assert data["tag"] == "v4.0.0" assert data["distance"] == 0 assert "commit_id" in data def test_describe_cli_require_tag_fails_without_tags(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) _make_commit(tmp_path, content=b"no-tags") result = runner.invoke(cli, ["describe", "--require-tag"], env=_env(tmp_path)) assert result.exit_code != 0 def test_describe_cli_long_flag(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"long") _make_tag(tmp_path, "v5.0.0", cid) result = runner.invoke(cli, ["describe", "--long"], env=_env(tmp_path)) assert result.exit_code == 0 assert "v5.0.0-0-sha256:" in result.output def test_describe_cli_short_flags(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) cid = _make_commit(tmp_path, content=b"short-flags") _make_tag(tmp_path, "v6.0.0", cid) result = runner.invoke(cli, ["describe", "-l", "--json"], env=_env(tmp_path)) assert result.exit_code == 0 data = json.loads(result.output) assert "v6.0.0" in data["name"] # --------------------------------------------------------------------------- # Stress: deep ancestry (100 commits, tag at root) # --------------------------------------------------------------------------- def test_describe_stress_deep_ancestry(tmp_path: pathlib.Path) -> None: _init_repo(tmp_path) prev: str | None = None first_commit_id = "" for i in range(100): cid = _make_commit(tmp_path, parent_id=prev, content=f"step {i}".encode()) if i == 0: first_commit_id = cid prev = cid _make_tag(tmp_path, "v-root", first_commit_id) assert prev is not None result = describe_commit(tmp_path, _REPO_ID, prev) assert result["tag"] == "v-root" assert result["distance"] == 99 assert "v-root~99" == result["name"] class TestRegisterFlags: def test_default_json_out_is_false(self) -> None: import argparse from muse.cli.commands.describe import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["describe"]) assert args.json_out is False def test_json_flag_sets_json_out(self) -> None: import argparse from muse.cli.commands.describe import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["describe", "--json"]) assert args.json_out is True def test_j_shorthand_sets_json_out(self) -> None: import argparse from muse.cli.commands.describe import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["describe", "-j"]) assert args.json_out is True