gabriel / muse public
test_diff_dir_tracking.py python
180 lines 6.4 KB
Raw
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 22 days ago
1 """Directory tracking in ``muse diff --json``.
2
3 Directories are first-class symbols in Muse — fully symmetric with files.
4 Empty directories appear in ``added``/``deleted`` with a trailing ``/``,
5 exactly as they do in ``muse status --json``.
6
7 The separate ``directories`` sub-object is gone. Agents check one place.
8
9 Coverage matrix
10 ---------------
11 DD Directory tracking in muse diff
12 DD1 staged empty dir → "foobar/" in added (not in a directories sub-key)
13 DD2 unstaged delete of committed dir → "emptydir/" in deleted
14 DD3 total_changes includes dir additions
15 DD4 directories key absent from muse diff --json output
16 DD5 dirs have trailing slash; regular files do not
17 DD6 clean repo — no trailing-slash entries in added or deleted
18 """
19
20 from __future__ import annotations
21
22 import json
23 import pathlib
24 from collections.abc import Mapping
25
26 import pytest
27
28 from tests.cli_test_helper import CliRunner
29
30 cli = None
31 runner = CliRunner()
32
33
34 # ---------------------------------------------------------------------------
35 # Helpers
36 # ---------------------------------------------------------------------------
37
38
39 def _env(root: pathlib.Path) -> Mapping[str, str]:
40 return {"MUSE_REPO_ROOT": str(root)}
41
42
43 def _run(root: pathlib.Path, *args: str) -> str:
44 result = runner.invoke(cli, list(args), env=_env(root))
45 assert result.exit_code == 0, f"{args} failed:\n{result.output}"
46 return result.output.strip()
47
48
49 def _diff(root: pathlib.Path, *extra: str) -> Mapping[str, object]:
50 result = runner.invoke(cli, ["diff", "--json"] + list(extra), env=_env(root))
51 assert result.exit_code == 0, f"diff --json failed:\n{result.output}"
52 return json.loads(result.output.strip())
53
54
55 # ---------------------------------------------------------------------------
56 # Fixtures
57 # ---------------------------------------------------------------------------
58
59
60 @pytest.fixture()
61 def base_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
62 """Code repo with one committed file, clean working tree."""
63 monkeypatch.chdir(tmp_path)
64 _run(tmp_path, "init", "--domain", "code")
65 (tmp_path / "main.py").write_text("x = 1\n")
66 _run(tmp_path, "code", "add", ".")
67 _run(tmp_path, "commit", "-m", "initial")
68 return tmp_path
69
70
71 @pytest.fixture()
72 def repo_with_committed_dir(
73 tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
74 ) -> pathlib.Path:
75 """Code repo with a committed empty directory ``emptydir``."""
76 monkeypatch.chdir(tmp_path)
77 _run(tmp_path, "init", "--domain", "code")
78 (tmp_path / "main.py").write_text("x = 1\n")
79 (tmp_path / "emptydir").mkdir()
80 _run(tmp_path, "code", "add", ".")
81 _run(tmp_path, "commit", "-m", "initial with emptydir")
82 return tmp_path
83
84
85 # ---------------------------------------------------------------------------
86 # DD Directory tracking in muse diff
87 # ---------------------------------------------------------------------------
88
89
90 class TestDiffDirTracking:
91 def test_DD1_staged_empty_dir_in_added(self, base_repo: pathlib.Path) -> None:
92 """DD1: staged empty dir → 'foobar/' in added (flat, not in a sub-key)."""
93 root = base_repo
94 (root / "foobar").mkdir()
95 _run(root, "code", "add", "foobar")
96
97 data = _diff(root, "--staged")
98
99 assert "foobar/" in data["added"], (
100 "staged empty dir must appear in added with trailing slash"
101 )
102
103 def test_DD2_deleted_committed_dir_in_deleted(
104 self, repo_with_committed_dir: pathlib.Path
105 ) -> None:
106 """DD2: unstaged delete of committed dir → 'emptydir/' in deleted."""
107 root = repo_with_committed_dir
108 (root / "emptydir").rmdir()
109
110 data = _diff(root)
111
112 assert "emptydir/" in data["deleted"], (
113 "deleted committed dir must appear in deleted with trailing slash"
114 )
115
116 def test_DD3_total_changes_includes_dir_additions(
117 self, base_repo: pathlib.Path
118 ) -> None:
119 """DD3: total_changes counts staged directory additions."""
120 root = base_repo
121 (root / "foobar").mkdir()
122 _run(root, "code", "add", "foobar")
123
124 data = _diff(root, "--staged")
125
126 expected = (
127 len(data["added"]) + len(data["modified"])
128 + len(data["deleted"]) + len(data["renamed"])
129 )
130 assert data["total_changes"] == expected
131 assert data["total_changes"] >= 1, "at least the new dir must count"
132
133 def test_DD4_no_directories_sub_key(self, base_repo: pathlib.Path) -> None:
134 """DD4: 'directories' key must be absent from muse diff --json output."""
135 root = base_repo
136 (root / "foobar").mkdir()
137 _run(root, "code", "add", "foobar")
138
139 data = _diff(root, "--staged")
140
141 assert "directories" not in data, (
142 "'directories' sub-object must not appear in diff output; "
143 "dirs are folded into added/deleted"
144 )
145
146 def test_DD5_dirs_have_trailing_slash_files_do_not(
147 self, base_repo: pathlib.Path
148 ) -> None:
149 """DD5: dirs have trailing slash; regular files do not."""
150 root = base_repo
151 (root / "newfile.py").write_text("y = 2\n")
152 (root / "newdir").mkdir()
153 _run(root, "code", "add", ".")
154
155 data = _diff(root, "--staged")
156
157 assert "newfile.py" in data["added"], "new file must appear without trailing slash"
158 assert "newdir/" in data["added"], "new dir must appear with trailing slash"
159 assert "newfile.py/" not in data["added"], "file must not have trailing slash"
160 assert "newdir" in data["added"] or "newdir/" in data["added"]
161 # Confirm the bare dir name (without slash) is not present
162 assert "newdir" not in [
163 p for p in data["added"] if not p.endswith("/")
164 ], "dir must appear with trailing slash only"
165
166 def test_DD6_clean_repo_no_trailing_slash_entries(
167 self, base_repo: pathlib.Path
168 ) -> None:
169 """DD6: clean staged diff → no trailing-slash entries in added or deleted."""
170 data = _diff(base_repo, "--staged")
171
172 trailing_in_added = [p for p in data["added"] if isinstance(p, str) and p.endswith("/")]
173 trailing_in_deleted = [p for p in data["deleted"] if isinstance(p, str) and p.endswith("/")]
174
175 assert trailing_in_added == [], (
176 f"clean diff must have no dir entries in added; got {trailing_in_added}"
177 )
178 assert trailing_in_deleted == [], (
179 f"clean diff must have no dir entries in deleted; got {trailing_in_deleted}"
180 )
File History 3 commits
sha256:81ae324db5ad375fbfe4834c6fcb378312cafad3cc92dec5d3e5c427306621a2 fix: remove commit_exists filter from have anchors — server… Sonnet 4.6 patch 22 days ago
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 23 days ago
sha256:09656d1b0772ea4c96f8911d7bf8042b33eb0596992c6546dfab3d21e9dee330 fix: align muse read --json schema and test contracts Sonnet 4.6 minor 24 days ago