"""Comprehensive tests for ``muse tag``. Covers: - Unit: write_tag, delete_tag, get_tags_for_commit, get_all_tags - Integration: add → list → remove round-trip - E2E: full CLI via CliRunner - Security: tag names sanitized, ref validation - Stress: many tags on many commits """ from __future__ import annotations import datetime import json import pathlib import pytest from tests.cli_test_helper import CliRunner from muse.core.types import fake_id, short_id from muse.core.paths import muse_dir, ref_path cli = None # argparse migration — CliRunner ignores this arg runner = CliRunner() # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _env(root: pathlib.Path) -> Manifest: return {"MUSE_REPO_ROOT": str(root)} def _init_repo(tmp_path: pathlib.Path) -> tuple[pathlib.Path, str]: dot_muse = muse_dir(tmp_path) dot_muse.mkdir() repo_id = fake_id("repo") (dot_muse / "repo.json").write_text(json.dumps({ "repo_id": repo_id, "domain": "midi", "default_branch": "main", "created_at": "2025-01-01T00:00:00+00:00", }), encoding="utf-8") (dot_muse / "HEAD").write_text("ref: refs/heads/main", 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 _make_commit( root: pathlib.Path, repo_id: str, branch: str = "main", message: str = "test" ) -> str: from muse.core.commits import ( CommitRecord, write_commit, ) from muse.core.snapshots import ( SnapshotRecord, write_snapshot, ) from muse.core.ids import hash_snapshot, hash_commit ref_file = ref_path(root, branch) parent_id = ref_file.read_text().strip() if ref_file.exists() else None manifest: Manifest = {} snap_id = hash_snapshot(manifest) committed_at = datetime.datetime.now(datetime.timezone.utc) commit_id = hash_commit( parent_ids=[parent_id] if parent_id else [], snapshot_id=snap_id, message=message, committed_at_iso=committed_at.isoformat(), ) write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest)) write_commit(root, CommitRecord( commit_id=commit_id, branch=branch, snapshot_id=snap_id, message=message, committed_at=committed_at, parent_commit_id=parent_id, )) ref_file.parent.mkdir(parents=True, exist_ok=True) ref_file.write_text(commit_id, encoding="utf-8") return commit_id # --------------------------------------------------------------------------- # Unit tests # --------------------------------------------------------------------------- class TestTagUnit: def test_write_and_read_tag(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) commit_id = _make_commit(root, repo_id) from muse.core.tags import ( TagRecord, get_tags_for_commit, write_tag, ) tag = TagRecord(tag_id=fake_id("tag"), repo_id=repo_id, commit_id=commit_id, tag="emotion:joyful") write_tag(root, tag) tags = get_tags_for_commit(root, repo_id, commit_id) assert len(tags) == 1 assert tags[0].tag == "emotion:joyful" def test_delete_tag(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) commit_id = _make_commit(root, repo_id) from muse.core.tags import ( TagRecord, delete_tag, get_tags_for_commit, write_tag, ) tag_id = fake_id("tag") write_tag(root, TagRecord(tag_id=tag_id, repo_id=repo_id, commit_id=commit_id, tag="section:chorus")) assert len(get_tags_for_commit(root, repo_id, commit_id)) == 1 assert delete_tag(root, repo_id, tag_id) is True assert get_tags_for_commit(root, repo_id, commit_id) == [] def test_delete_nonexistent_tag_returns_false(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) from muse.core.tags import delete_tag assert delete_tag(root, repo_id, fake_id("tag")) is False def test_get_all_tags_empty(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) from muse.core.tags import get_all_tags assert get_all_tags(root, repo_id) == [] # --------------------------------------------------------------------------- # Content-addressed tag_id # --------------------------------------------------------------------------- class TestTagIdContentAddressed: """tag_id must be sha256: of genesis content, not a UUID.""" def test_tag_id_is_sha256_prefixed(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) commit_id = _make_commit(root, repo_id) from muse.core.tags import compute_tag_id tag_id = compute_tag_id(repo_id=repo_id, commit_id=commit_id, tag="emotion:joyful") assert tag_id.startswith("sha256:"), f"Expected sha256: prefix, got {tag_id!r}" assert len(tag_id) == 71 def test_tag_id_is_deterministic(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) commit_id = _make_commit(root, repo_id) from muse.core.tags import compute_tag_id id1 = compute_tag_id(repo_id=repo_id, commit_id=commit_id, tag="v1.0") id2 = compute_tag_id(repo_id=repo_id, commit_id=commit_id, tag="v1.0") assert id1 == id2 def test_tag_id_differs_by_tag_name(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) commit_id = _make_commit(root, repo_id) from muse.core.tags import compute_tag_id assert compute_tag_id(repo_id, commit_id, "v1.0") != compute_tag_id(repo_id, commit_id, "v2.0") def test_tag_id_differs_by_commit(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) c1 = _make_commit(root, repo_id, message="first") c2 = _make_commit(root, repo_id, message="second") from muse.core.tags import compute_tag_id assert compute_tag_id(repo_id, c1, "v1.0") != compute_tag_id(repo_id, c2, "v1.0") def test_tag_id_is_sha256_not_uuid4(self, tmp_path: pathlib.Path) -> None: import re root, repo_id = _init_repo(tmp_path) commit_id = _make_commit(root, repo_id) from muse.core.tags import compute_tag_id tag_id = compute_tag_id(repo_id, commit_id, "release:1.0") assert tag_id.startswith("sha256:") 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(tag_id) def test_cli_tag_add_returns_content_addressed_id(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) commit_id = _make_commit(root, repo_id) result = runner.invoke(cli, ["tag", "add", "v1.0", commit_id, "--json"], env=_env(root)) assert result.exit_code == 0 data = json.loads(result.output) tag_id = data["tag_id"] assert tag_id.startswith("sha256:"), f"Expected sha256: prefix, got {tag_id!r}" assert len(tag_id) == 71 def test_write_tag_uses_content_addressed_id(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) commit_id = _make_commit(root, repo_id) from muse.core.tags import ( TagRecord, compute_tag_id, get_tags_for_commit, write_tag, ) expected_id = compute_tag_id(repo_id, commit_id, "emotion:sad") tag = TagRecord(tag_id=expected_id, repo_id=repo_id, commit_id=commit_id, tag="emotion:sad") write_tag(root, tag) tags = get_tags_for_commit(root, repo_id, commit_id) assert tags[0].tag_id == expected_id # --------------------------------------------------------------------------- # Integration tests # --------------------------------------------------------------------------- class TestTagIntegration: def test_add_and_list_tag(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) _make_commit(root, repo_id) result = runner.invoke(cli, ["tag", "add", "emotion:joyful"], env=_env(root), catch_exceptions=False) assert result.exit_code == 0 assert "Tagged" in result.output result2 = runner.invoke(cli, ["tag", "list"], env=_env(root), catch_exceptions=False) assert "emotion:joyful" in result2.output def test_list_tags_for_specific_commit(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) commit_id = _make_commit(root, repo_id) runner.invoke(cli, ["tag", "add", "section:verse"], env=_env(root), catch_exceptions=False) result = runner.invoke(cli, ["tag", "list", short_id(commit_id)], env=_env(root), catch_exceptions=False) assert "section:verse" in result.output def test_remove_tag(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) _make_commit(root, repo_id) runner.invoke(cli, ["tag", "add", "emotion:tense"], env=_env(root), catch_exceptions=False) result = runner.invoke(cli, ["tag", "remove", "emotion:tense"], env=_env(root), catch_exceptions=False) assert result.exit_code == 0 assert "Removed" in result.output result2 = runner.invoke(cli, ["tag", "list"], env=_env(root), catch_exceptions=False) assert "emotion:tense" not in result2.output def test_remove_nonexistent_tag_is_idempotent(self, tmp_path: pathlib.Path) -> None: """Removing a tag that doesn't exist exits 0 (idempotent) with not_found status.""" root, repo_id = _init_repo(tmp_path) _make_commit(root, repo_id) result = runner.invoke(cli, ["tag", "remove", "ghost:tag", "--json"], env=_env(root)) assert result.exit_code == 0 import json as _json d = _json.loads(result.output) assert d["status"] == "not_found" assert d["removed_count"] == 0 def test_add_multiple_tags_same_commit(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) _make_commit(root, repo_id) runner.invoke(cli, ["tag", "add", "key:Am"], env=_env(root), catch_exceptions=False) runner.invoke(cli, ["tag", "add", "tempo:120bpm"], env=_env(root), catch_exceptions=False) result = runner.invoke(cli, ["tag", "list"], env=_env(root), catch_exceptions=False) assert "key:Am" in result.output assert "tempo:120bpm" in result.output def test_tag_on_invalid_ref_fails(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) _make_commit(root, repo_id) result = runner.invoke(cli, ["tag", "add", "emotion:sad", "deadbeef" * 8], env=_env(root)) assert result.exit_code != 0 # --------------------------------------------------------------------------- # Security tests # --------------------------------------------------------------------------- class TestTagSecurity: def test_tag_with_control_characters_sanitized_in_output( self, tmp_path: pathlib.Path ) -> None: root, repo_id = _init_repo(tmp_path) _make_commit(root, repo_id) malicious = "emotion:\x1b[31mred\x1b[0m" runner.invoke(cli, ["tag", "add", malicious], env=_env(root), catch_exceptions=False) result = runner.invoke(cli, ["tag", "list"], env=_env(root), catch_exceptions=False) assert result.exit_code == 0 assert "\x1b" not in result.output # --------------------------------------------------------------------------- # Stress tests # --------------------------------------------------------------------------- class TestTagStress: def test_many_tags_on_many_commits(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) commit_ids = [_make_commit(root, repo_id, message=f"commit {i}") for i in range(30)] from muse.core.tags import ( TagRecord, get_all_tags, write_tag, ) tag_types = ["emotion:joyful", "section:chorus", "key:Am", "tempo:120bpm", "stage:master"] for i, cid in enumerate(commit_ids): write_tag(root, TagRecord( tag_id=fake_id(f"tag-{i}"), repo_id=repo_id, commit_id=cid, tag=tag_types[i % len(tag_types)], )) all_tags = get_all_tags(root, repo_id) assert len(all_tags) == 30 result = runner.invoke(cli, ["tag", "list"], env=_env(root), catch_exceptions=False) assert result.exit_code == 0 for tag_type in tag_types: assert tag_type in result.output