"""TDD — granular 3-tier stat in muse read output. RD-1 Structured-delta path: directory insert counted as directory, not file RD-2 Structured-delta path: symbol ops appear in Symbols line RD-3 Fallback path: directory addition shows "A test/" with trailing slash RD-4 Fallback path: summary says "Directories: 1 added" not "N file(s) changed" RD-5 Fallback path: file + directory counted separately in summary """ from __future__ import annotations import datetime import json import pathlib from collections.abc import Mapping import pytest from tests.cli_test_helper import CliRunner from muse.core.paths import muse_dir, ref_path 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.types import blob_id runner = CliRunner() # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _init_repo(path: pathlib.Path) -> pathlib.Path: dot = muse_dir(path) for d in ("commits", "snapshots", "objects", "refs/heads", "code"): (dot / d).mkdir(parents=True, exist_ok=True) (dot / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8") (dot / "repo.json").write_text( json.dumps({"repo_id": "read-stat-test", "domain": "code"}), encoding="utf-8", ) return path def _make_commit( root: pathlib.Path, files: Mapping[str, bytes], parent: str | None = None, directories: list[str] | None = None, ) -> str: manifest: dict[str, str] = {} for rel, content in files.items(): oid = blob_id(content) write_object(root, oid, content) manifest[rel] = oid snap_id = hash_snapshot(manifest, directories or []) write_snapshot(root, SnapshotRecord( snapshot_id=snap_id, manifest=manifest, directories=directories or [] )) committed_at = datetime.datetime.now(datetime.timezone.utc) commit_id = hash_commit( parent_ids=[parent] if parent else [], snapshot_id=snap_id, message="test commit", committed_at_iso=committed_at.isoformat(), ) write_commit(root, CommitRecord( commit_id=commit_id, branch="main", snapshot_id=snap_id, message="test commit", committed_at=committed_at, parent_commit_id=parent, )) ref_path(root, "main").write_text(commit_id, encoding="utf-8") return commit_id def _env(root: pathlib.Path) -> Mapping[str, str]: return {"MUSE_REPO_ROOT": str(root)} def _read(root: pathlib.Path) -> str: return runner.invoke(None, ["read"], env=_env(root)).output # --------------------------------------------------------------------------- # RD-3 Fallback path: directory shown as "A test/" with trailing slash # --------------------------------------------------------------------------- class TestFallbackPathDirectoryDisplay: def test_directory_shows_with_trailing_slash( self, tmp_path: pathlib.Path ) -> None: root = _init_repo(tmp_path) c1 = _make_commit(root, {"readme.md": b"# hi\n"}) _make_commit(root, {"readme.md": b"# hi\n"}, parent=c1, directories=["test"]) out = _read(root) assert "A test/" in out, f"Expected 'A test/' in:\n{out}" def test_directory_not_shown_without_slash( self, tmp_path: pathlib.Path ) -> None: root = _init_repo(tmp_path) c1 = _make_commit(root, {"readme.md": b"# hi\n"}) _make_commit(root, {"readme.md": b"# hi\n"}, parent=c1, directories=["test"]) out = _read(root) assert "A test\n" not in out, f"'A test' (no slash) must not appear in:\n{out}" # --------------------------------------------------------------------------- # RD-4 Fallback path: summary shows granular directory/file breakdown # --------------------------------------------------------------------------- class TestFallbackPathSummary: def test_directory_only_shows_directory_summary( self, tmp_path: pathlib.Path ) -> None: """Adding only an empty dir: summary must say 'directory', not 'file(s)'.""" root = _init_repo(tmp_path) c1 = _make_commit(root, {"readme.md": b"# hi\n"}) _make_commit(root, {"readme.md": b"# hi\n"}, parent=c1, directories=["test"]) out = _read(root) assert "director" in out.lower(), f"Expected 'directory' in summary:\n{out}" assert "file(s) changed" not in out, ( f"'file(s) changed' must not appear for directory-only commit:\n{out}" ) def test_file_only_shows_file_summary( self, tmp_path: pathlib.Path ) -> None: root = _init_repo(tmp_path) c1 = _make_commit(root, {"readme.md": b"# hi\n"}) _make_commit(root, {"readme.md": b"# hi\n", "new.py": b"x=1\n"}, parent=c1) out = _read(root) assert "file" in out.lower() assert "file(s) changed" not in out def test_file_and_directory_counted_separately( self, tmp_path: pathlib.Path ) -> None: root = _init_repo(tmp_path) c1 = _make_commit(root, {"readme.md": b"# hi\n"}) _make_commit( root, {"readme.md": b"# hi\n", "new.py": b"x=1\n"}, parent=c1, directories=["mydir"], ) out = _read(root) assert "director" in out.lower() assert "file" in out.lower() # --------------------------------------------------------------------------- # RD-1 Structured-delta path: directory op not counted as file # --------------------------------------------------------------------------- class TestStructuredDeltaPath: def test_directory_insert_not_counted_as_file( self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: """After a real muse commit that adds a dir, read --stat must say directory.""" root = _init_repo(tmp_path) _make_commit(root, {"readme.md": b"# hi\n"}) monkeypatch.chdir(root) (root / "mydir").mkdir() runner.invoke(None, ["code", "add", "mydir/"], env=_env(root)) runner.invoke(None, ["commit", "-m", "add mydir"], env=_env(root)) out = _read(root) assert "director" in out.lower(), f"Expected 'directory' in:\n{out}" # Files line must not count the directory if "Files:" in out: files_line = [l for l in out.splitlines() if "Files:" in l][0] assert "mydir" not in files_line, ( f"Directory must not appear in Files line: {files_line}" )