"""Supercharge tests for ``muse release``. Coverage tiers -------------- - Unit: _short_id helper — sha256:-prefixed input - Integration: duration_ms + exit_code on every JSON path (add, list, read, push --dry-run, delete --dry-run, suggest) - Data: short commit IDs in text output (not bare truncated hex) - Data: list JSON wraps in {total, releases} envelope - Security: no tracebacks; ANSI stripped in text - Performance: suggest on 50 commits under 500ms """ from __future__ import annotations import datetime import json import pathlib import time from muse.core.errors import ExitCode from tests.cli_test_helper import CliRunner, InvokeResult from muse.core.types import content_hash, long_id, short_id as _short_id from muse.core.paths import ref_path, muse_dir runner = CliRunner() _SHA256_SHORT_19 = __import__("re").compile(r"^sha256:[0-9a-f]{12}$") # --------------------------------------------------------------------------- # Repo + commit helpers (mirrors test_release.py) # --------------------------------------------------------------------------- def _make_repo(tmp_path: pathlib.Path) -> tuple[pathlib.Path, str]: dot_muse = muse_dir(tmp_path) dot_muse.mkdir() repo_id = content_hash({"path": str(tmp_path), "domain": "code", "created_at": "2025-01-01T00:00:00+00:00"}) (dot_muse / "repo.json").write_text( json.dumps({"repo_id": repo_id, "domain": "code", "default_branch": "main", "created_at": "2025-01-01T00:00:00+00:00"}), encoding="utf-8", ) (dot_muse / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8") (dot_muse / "refs" / "heads").mkdir(parents=True) (dot_muse / "snapshots").mkdir() (dot_muse / "commits").mkdir() (dot_muse / "objects").mkdir() return tmp_path, repo_id def _commit( root: pathlib.Path, repo_id: str, *, message: str = "feat: add something", sem_ver_bump: str = "minor", agent_id: str = "claude-code", model_id: str = "claude-sonnet-4-6", ) -> str: from muse.core.ids import hash_commit as compute_commit_id, hash_snapshot as compute_snapshot_id from muse.core.commits import ( CommitRecord, write_commit, ) from muse.core.snapshots import ( SnapshotRecord, write_snapshot, ) from muse.domain import SemVerBump branch = "main" ref_file = ref_path(root, branch) raw_parent = ref_file.read_text().strip() if ref_file.exists() else "" parent_id: str | None = raw_parent if raw_parent else None snap_id = compute_snapshot_id({}) write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest={})) now = datetime.datetime.now(datetime.timezone.utc) parent_ids = [parent_id] if parent_id else [] commit_id = compute_commit_id( parent_ids=parent_ids, snapshot_id=snap_id, message=message, committed_at_iso=now.isoformat(), ) bump: SemVerBump = sem_ver_bump # type: ignore[assignment] write_commit(root, CommitRecord( commit_id=commit_id, branch=branch, snapshot_id=snap_id, message=message, committed_at=now, parent_commit_id=parent_id, sem_ver_bump=bump, breaking_changes=[], agent_id=agent_id, model_id=model_id, )) ref_file.write_text(commit_id, encoding="utf-8") return commit_id def _invoke(root: pathlib.Path, *args: str) -> InvokeResult: from muse.cli.app import main as cli return runner.invoke(cli, ["release", *args], env={"MUSE_REPO_ROOT": str(root)}) def _add(root: pathlib.Path, tag: str = "v0.1.0", **kwargs: str) -> InvokeResult: extra = [] for k, v in kwargs.items(): extra.extend([f"--{k}", v]) return _invoke(root, "add", tag, "--json", *extra) # --------------------------------------------------------------------------- # Unit — _short_id # --------------------------------------------------------------------------- class TestShortId: """_short_id normalises sha256:-prefixed commit IDs to sha256:<12-hex>.""" def test_sha256_prefixed_returns_sha256_short(self) -> None: cid = long_id("a" * 64) assert _short_id(cid) == long_id("a" * 12) def test_result_is_19_chars(self) -> None: assert len(_short_id(long_id("b" * 64))) == 19 def test_bare_hex_also_handled(self) -> None: # bare hex in → first 12 chars out, no prefix added result = _short_id("c" * 64) assert result == "c" * 12 def test_matches_short_regex(self) -> None: assert _SHA256_SHORT_19.match(_short_id(long_id("d" * 64))) def test_idempotent_on_short_input(self) -> None: """Already-short sha256:<12-hex> passes through unchanged.""" short = long_id("e" * 12) assert _short_id(short) == short # --------------------------------------------------------------------------- # Integration — duration_ms and exit_code on every JSON path # --------------------------------------------------------------------------- class TestDurationAndExitCode: """Every subcommand's JSON output must carry duration_ms and exit_code.""" def test_add_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) data = json.loads(_add(root).output) assert "duration_ms" in data def test_add_json_exit_code_zero(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) data = json.loads(_add(root).output) assert data["exit_code"] == 0 def test_add_duration_ms_is_float(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) assert isinstance(json.loads(_add(root).output)["duration_ms"], float) def test_list_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root) data = json.loads(_invoke(root, "list", "--json").output) assert "duration_ms" in data def test_list_json_exit_code_zero(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) data = json.loads(_invoke(root, "list", "--json").output) assert data["exit_code"] == 0 def test_list_empty_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None: """Even with zero releases, metadata fields must be present.""" root, repo_id = _make_repo(tmp_path) data = json.loads(_invoke(root, "list", "--json").output) assert "duration_ms" in data def test_read_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root) data = json.loads(_invoke(root, "read", "v0.1.0", "--json").output) assert "duration_ms" in data def test_read_json_exit_code_zero(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root) data = json.loads(_invoke(root, "read", "v0.1.0", "--json").output) assert data["exit_code"] == 0 def test_push_dry_run_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root) data = json.loads( _invoke(root, "push", "v0.1.0", "--dry-run", "--json").output ) assert "duration_ms" in data def test_push_dry_run_json_exit_code_zero(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root) data = json.loads( _invoke(root, "push", "v0.1.0", "--dry-run", "--json").output ) assert data["exit_code"] == 0 def test_delete_dry_run_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root) data = json.loads( _invoke(root, "delete", "v0.1.0", "--dry-run", "--json").output ) assert "duration_ms" in data def test_delete_dry_run_json_exit_code_zero(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root) data = json.loads( _invoke(root, "delete", "v0.1.0", "--dry-run", "--json").output ) assert data["exit_code"] == 0 def test_suggest_json_has_duration_ms(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id, sem_ver_bump="minor") data = json.loads(_invoke(root, "suggest", "--json").output) assert "duration_ms" in data def test_suggest_json_exit_code_zero(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id, sem_ver_bump="minor") data = json.loads(_invoke(root, "suggest", "--json").output) assert data["exit_code"] == 0 def test_duration_ms_non_negative(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root) assert json.loads(_add(root, "v0.2.0").output)["duration_ms"] >= 0.0 def test_duration_ms_3dp_precision(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) ms = json.loads(_add(root).output)["duration_ms"] assert round(ms, 3) == ms # --------------------------------------------------------------------------- # Data integrity — text format short IDs # --------------------------------------------------------------------------- class TestTextFormatShortId: """Text output must show sha256:<12-hex> commit IDs, not bare [:8] slices.""" def _short_tokens(self, text: str) -> list[str]: return [tok for tok in text.split() if _SHA256_SHORT_19.match(tok)] def test_read_text_shows_sha256_short_commit(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) cid = _commit(root, repo_id) _invoke(root, "add", "v0.1.0") result = _invoke(root, "read", "v0.1.0") assert result.exit_code == 0 tokens = self._short_tokens(result.output) assert tokens, f"no sha256:<12-hex> token in read text:\n{result.output}" def test_list_text_shows_sha256_short_commit(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _invoke(root, "add", "v0.1.0") result = _invoke(root, "list") assert result.exit_code == 0 tokens = self._short_tokens(result.output) assert tokens, f"no sha256:<12-hex> token in list text:\n{result.output}" def test_suggest_text_shows_sha256_short_in_driver(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id, sem_ver_bump="minor") result = _invoke(root, "suggest") assert result.exit_code == 0 tokens = self._short_tokens(result.output) assert tokens, f"no sha256:<12-hex> token in suggest text:\n{result.output}" def test_read_text_commit_not_bare_hex_prefix_only(self, tmp_path: pathlib.Path) -> None: """Regression: commit_id[:8] on sha256:-prefixed ID yields 'sha256:a' — wrong.""" root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _invoke(root, "add", "v0.1.0") result = _invoke(root, "read", "v0.1.0") # "sha256:a" with no further hex after colon would indicate the bug assert "sha256:a\n" not in result.output assert "sha256:b\n" not in result.output def test_changelog_entries_show_sha256_short(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id, message="feat: first", sem_ver_bump="minor") _commit(root, repo_id, message="feat: second", sem_ver_bump="minor") _invoke(root, "add", "v0.1.0") result = _invoke(root, "read", "v0.1.0") tokens = self._short_tokens(result.output) # changelog has 2 entries, each with a short commit ID assert len(tokens) >= 2, f"expected ≥2 sha256:<12-hex> tokens:\n{result.output}" # --------------------------------------------------------------------------- # Data integrity — list JSON envelope # --------------------------------------------------------------------------- class TestListJsonEnvelope: """list --json emits {total, releases, duration_ms, exit_code} — not a bare array.""" def test_list_json_top_level_is_object(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) data = json.loads(_invoke(root, "list", "--json").output) assert isinstance(data, dict) def test_list_json_has_total_key(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) data = json.loads(_invoke(root, "list", "--json").output) assert "total" in data def test_list_json_has_releases_key(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) data = json.loads(_invoke(root, "list", "--json").output) assert "releases" in data def test_list_json_releases_is_array(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) data = json.loads(_invoke(root, "list", "--json").output) assert isinstance(data["releases"], list) def test_list_json_total_matches_releases_length(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root, "v0.1.0") _commit(root, repo_id) _add(root, "v0.2.0") data = json.loads(_invoke(root, "list", "--json").output) assert data["total"] == len(data["releases"]) == 2 def test_list_json_empty_total_is_zero(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) data = json.loads(_invoke(root, "list", "--json").output) assert data["total"] == 0 assert data["releases"] == [] def test_bare_release_json_flag_uses_envelope(self, tmp_path: pathlib.Path) -> None: """`muse release --json` (no subcommand) also uses the envelope.""" root, repo_id = _make_repo(tmp_path) data = json.loads(_invoke(root, "--json").output) assert "releases" in data assert "total" in data # --------------------------------------------------------------------------- # Security # --------------------------------------------------------------------------- class TestSecuritySupercharge: def test_no_traceback_on_bad_semver(self, tmp_path: pathlib.Path) -> None: root, _ = _make_repo(tmp_path) result = _invoke(root, "add", "not-semver", "--json") assert result.exit_code == ExitCode.USER_ERROR assert "Traceback" not in result.output def test_no_traceback_on_missing_read(self, tmp_path: pathlib.Path) -> None: root, _ = _make_repo(tmp_path) result = _invoke(root, "read", "v9.9.9", "--json") assert result.exit_code == ExitCode.NOT_FOUND assert "Traceback" not in result.output def test_no_traceback_on_suggest_no_commits(self, tmp_path: pathlib.Path) -> None: root, _ = _make_repo(tmp_path) result = _invoke(root, "suggest", "--json") # Either no commits (exit 1) or 0 unreleased — must not traceback assert "Traceback" not in result.output def test_add_json_error_on_duplicate(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) _add(root) result = _invoke(root, "add", "v0.1.0", "--json") assert result.exit_code == ExitCode.USER_ERROR # JSON error is on stdout (first line); ❌ message goes to stderr first_line = result.output.splitlines()[0] data = json.loads(first_line) assert data["error"] == "already_exists" # --------------------------------------------------------------------------- # Performance # --------------------------------------------------------------------------- class TestPerformanceSupercharge: def test_list_50_releases_under_500ms(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) for i in range(50): _commit(root, repo_id, message=f"feat: thing {i}") _invoke(root, "add", f"v0.{i}.0") t0 = time.monotonic() result = _invoke(root, "list", "--json") duration_ms = (time.monotonic() - t0) * 1000 assert result.exit_code == 0 assert duration_ms < 500 def test_suggest_50_commits_under_500ms(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) for i in range(50): _commit(root, repo_id, sem_ver_bump="patch") t0 = time.monotonic() result = _invoke(root, "suggest", "--json") duration_ms = (time.monotonic() - t0) * 1000 assert result.exit_code == 0 assert duration_ms < 500 def test_duration_ms_plausible(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) ms = json.loads(_add(root).output)["duration_ms"] assert 0.0 <= ms < 500 # --------------------------------------------------------------------------- # Content-addressed release_id # --------------------------------------------------------------------------- class TestReleaseIdContentAddressed: """release_id must be sha256: of genesis content, not a UUID.""" def test_release_id_is_sha256_prefixed(self, tmp_path: pathlib.Path) -> None: root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) data = json.loads(_add(root, "v1.0.0").output) assert data["release_id"].startswith("sha256:"), f"Expected sha256: prefix, got {data['release_id']!r}" assert len(data["release_id"]) == 71 def test_release_id_is_sha256_not_uuid4(self, tmp_path: pathlib.Path) -> None: import re root, repo_id = _make_repo(tmp_path) _commit(root, repo_id) data = json.loads(_add(root, "v1.0.0").output) uuid4_re = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$") assert not uuid4_re.match(data["release_id"]) def test_release_id_is_deterministic(self, tmp_path: pathlib.Path) -> None: """Same repo + tag + commit → same release_id.""" from muse.core.releases import compute_release_id release_id = compute_release_id(repo_id="test-repo", tag="v1.0.0", commit_id=long_id("a" * 64)) assert release_id == compute_release_id(repo_id="test-repo", tag="v1.0.0", commit_id=long_id("a" * 64)) def test_release_id_differs_by_tag(self, tmp_path: pathlib.Path) -> None: from muse.core.releases import compute_release_id cid = long_id("a" * 64) assert compute_release_id("repo", "v1.0.0", cid) != compute_release_id("repo", "v2.0.0", cid) def test_release_id_differs_by_commit(self, tmp_path: pathlib.Path) -> None: from muse.core.releases import compute_release_id assert compute_release_id("repo", "v1.0.0", long_id("a" * 64)) != compute_release_id("repo", "v1.0.0", long_id("b" * 64)) class TestRegisterFlags: def test_default_json_out_is_false(self) -> None: import argparse from muse.cli.commands.release import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["release"]) assert args.json_out is False def test_json_flag_sets_json_out(self) -> None: import argparse from muse.cli.commands.release import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["release", "--json"]) assert args.json_out is True def test_j_shorthand_sets_json_out(self) -> None: import argparse from muse.cli.commands.release import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["release", "-j"]) assert args.json_out is True