"""muse#53 item 10 -- `muse symlog stat [FILE::SYMBOL] [--file path] [--json]`. Summary statistics over a symbol's (or an entire file's) symlog journal. """ from __future__ import annotations import json import os import pathlib import textwrap from tests.cli_test_helper import CliRunner runner = CliRunner() def _invoke(repo: pathlib.Path, args: list[str]): saved = os.getcwd() try: os.chdir(repo) return runner.invoke(None, args) finally: os.chdir(saved) def _init_repo(repo: pathlib.Path) -> None: repo.mkdir(parents=True, exist_ok=True) _invoke(repo, ["init"]) def _write_file(repo: pathlib.Path, rel: str, content: str) -> None: p = repo / rel p.parent.mkdir(parents=True, exist_ok=True) p.write_text(textwrap.dedent(content)) def _commit(repo: pathlib.Path, msg: str, author: str = "claude-code") -> str: _invoke(repo, ["code", "add", "."]) result = _invoke(repo, [ "commit", "-m", msg, "--agent-id", "claude-code", "--model-id", "claude-sonnet-4-6", "--author", author, "--json", ]) data = json.loads(result.stdout) return data["commit_id"] def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path: _init_repo(tmp_path) return tmp_path def _symlog(repo: pathlib.Path, *args: str): return _invoke(repo, ["symlog", *args]) class TestSymlogStatSingleSymbol: def test_empty_symlog_reports_zero(self, tmp_path: pathlib.Path) -> None: repo = _make_repo(tmp_path) result = _symlog(repo, "stat", "src/billing.py::compute_total", "--json") data = json.loads(result.stdout) assert data["symbol"] == "src/billing.py::compute_total" assert data["total_entries"] == 0 assert data["operation_breakdown"] == {} assert data["most_active_author"] is None assert data["first_commit_id"] is None assert data["last_commit_id"] is None def test_counts_created_and_modified(self, tmp_path: pathlib.Path) -> None: repo = _make_repo(tmp_path) _write_file(repo, "src/billing.py", "def compute_total(items):\n return sum(items)\n") _commit(repo, "add compute_total") _write_file(repo, "src/billing.py", "def compute_total(items):\n return sum(items) + 1\n") _commit(repo, "fix off-by-one") result = _symlog(repo, "stat", "src/billing.py::compute_total", "--json") data = json.loads(result.stdout) assert data["total_entries"] == 2 assert data["operation_breakdown"]["symbol-created"] == 1 assert data["operation_breakdown"]["symbol-modified"] == 1 assert data["most_active_author"] == "claude-code" assert data["first_commit_id"] is not None assert data["last_commit_id"] is not None def test_missing_symbol_and_file_errors(self, tmp_path: pathlib.Path) -> None: repo = _make_repo(tmp_path) result = _symlog(repo, "stat", "--json") assert result.exit_code == 1 def test_symbol_and_file_both_given_errors(self, tmp_path: pathlib.Path) -> None: repo = _make_repo(tmp_path) result = _symlog(repo, "stat", "src/billing.py::compute_total", "--file", "src/billing.py", "--json") assert result.exit_code == 1 def test_invalid_symbol_address_errors(self, tmp_path: pathlib.Path) -> None: repo = _make_repo(tmp_path) result = _symlog(repo, "stat", "no-double-colon-here", "--json") assert result.exit_code == 1 class TestSymlogStatFileMode: def test_aggregates_across_symbols_in_file(self, tmp_path: pathlib.Path) -> None: repo = _make_repo(tmp_path) _write_file(repo, "src/billing.py", ( "def compute_total(items):\n return sum(items)\n\n" "def apply_discount(total, pct):\n return total * (1 - pct)\n" )) _commit(repo, "add two functions") result = _symlog(repo, "stat", "--file", "src/billing.py", "--json") data = json.loads(result.stdout) assert data["file"] == "src/billing.py" assert data["symbol"] is None assert data["total_entries"] == 2 assert data["operation_breakdown"]["symbol-created"] == 2 def test_file_with_no_symlogs_reports_zero(self, tmp_path: pathlib.Path) -> None: repo = _make_repo(tmp_path) result = _symlog(repo, "stat", "--file", "src/nonexistent.py", "--json") data = json.loads(result.stdout) assert data["total_entries"] == 0