gabriel / muse public
test_code_reset_granular.py python
213 lines 7.7 KB
Raw
sha256:a154bc65916614c833d5a40a10d81ba3eae0d0495b0afddd34dc34f18d5e91b8 fix: test suite alignment and typing audit — zero violations Sonnet 4.6 minor ⚠ breaking 22 days ago
1 """TDD — granular unstage summary for muse code reset.
2
3 RS-1 Unstage all: output counts dirs, files, and symbols separately.
4 RS-2 Unstage specific dir: output says "1 directory", not "1 file(s)".
5 RS-3 Unstage specific file: output says "1 file", not "1 file(s)".
6 RS-4 Mixed unstage (dir + file + symbol): all three counts appear.
7 RS-5 Dry-run unstage all: preview message uses same granular format.
8 RS-6 Dry-run unstage specific: preview message uses same granular format.
9 """
10 from __future__ import annotations
11
12 import datetime
13 import json
14 import pathlib
15 from collections.abc import Mapping
16
17 import pytest
18
19 from tests.cli_test_helper import CliRunner
20 from muse.core.paths import muse_dir, ref_path
21 from muse.core.object_store import write_object
22 from muse.core.ids import hash_commit, hash_snapshot
23 from muse.core.commits import CommitRecord, write_commit
24 from muse.core.snapshots import SnapshotRecord, write_snapshot
25 from muse.core.types import blob_id
26
27 runner = CliRunner()
28
29
30 # ---------------------------------------------------------------------------
31 # Helpers
32 # ---------------------------------------------------------------------------
33
34 def _init_repo(path: pathlib.Path) -> pathlib.Path:
35 dot = muse_dir(path)
36 for d in ("commits", "snapshots", "objects", "refs/heads", "code"):
37 (dot / d).mkdir(parents=True, exist_ok=True)
38 (dot / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
39 (dot / "repo.json").write_text(
40 json.dumps({"repo_id": "reset-test", "domain": "code"}),
41 encoding="utf-8",
42 )
43 return path
44
45
46 def _make_commit(root: pathlib.Path, files: Mapping[str, bytes]) -> str:
47 manifest: dict[str, str] = {}
48 for rel, content in files.items():
49 oid = blob_id(content)
50 write_object(root, oid, content)
51 manifest[rel] = oid
52 snap_id = hash_snapshot(manifest)
53 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
54 committed_at = datetime.datetime.now(datetime.timezone.utc)
55 commit_id = hash_commit(
56 parent_ids=[], snapshot_id=snap_id,
57 message="init", committed_at_iso=committed_at.isoformat(),
58 )
59 write_commit(root, CommitRecord(
60 commit_id=commit_id, branch="main", snapshot_id=snap_id,
61 message="init", committed_at=committed_at,
62 ))
63 ref_path(root, "main").write_text(commit_id, encoding="utf-8")
64 return commit_id
65
66
67 def _env(root: pathlib.Path) -> Mapping[str, str]:
68 return {"MUSE_REPO_ROOT": str(root)}
69
70
71 def _stage_dir(root: pathlib.Path, dirname: str) -> None:
72 (root / dirname).mkdir(exist_ok=True)
73 runner.invoke(None, ["code", "add", dirname + "/"], env=_env(root))
74
75
76 def _stage_file(root: pathlib.Path, filename: str, content: bytes = b"x") -> None:
77 (root / filename).write_bytes(content)
78 runner.invoke(None, ["code", "add", filename], env=_env(root))
79
80
81 # ---------------------------------------------------------------------------
82 # RS-1 Unstage all: granular counts
83 # ---------------------------------------------------------------------------
84
85 class TestResetAllGranular:
86 def test_unstage_all_dirs_says_directory(
87 self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
88 ) -> None:
89 """Unstage all when only a dir is staged: output must say 'directory'."""
90 root = _init_repo(tmp_path)
91 _make_commit(root, {"readme.md": b"# hi\n"})
92 monkeypatch.chdir(root)
93 _stage_dir(root, "mydir")
94
95 result = runner.invoke(None, ["code", "reset"], env=_env(root))
96
97 assert result.exit_code == 0
98 assert "directory" in result.output
99 assert "file" not in result.output, (
100 f"'file' must not appear when only a dir was staged:\n{result.output}"
101 )
102
103 def test_unstage_all_files_says_file(
104 self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
105 ) -> None:
106 """Unstage all when only a file is staged: output must say 'file'."""
107 root = _init_repo(tmp_path)
108 _make_commit(root, {"readme.md": b"# hi\n"})
109 monkeypatch.chdir(root)
110 _stage_file(root, "new.py")
111
112 result = runner.invoke(None, ["code", "reset"], env=_env(root))
113
114 assert result.exit_code == 0
115 assert "file" in result.output
116 assert "directory" not in result.output
117
118 def test_unstage_all_no_file_s_plural(
119 self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
120 ) -> None:
121 """The old 'file(s)' format must be gone."""
122 root = _init_repo(tmp_path)
123 _make_commit(root, {"readme.md": b"# hi\n"})
124 monkeypatch.chdir(root)
125 _stage_file(root, "new.py")
126
127 result = runner.invoke(None, ["code", "reset"], env=_env(root))
128
129 assert "file(s)" not in result.output
130
131
132 # ---------------------------------------------------------------------------
133 # RS-2 Unstage specific dir: says 'directory'
134 # ---------------------------------------------------------------------------
135
136 class TestResetSpecificDir:
137 def test_unstage_dir_says_directory(
138 self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
139 ) -> None:
140 root = _init_repo(tmp_path)
141 _make_commit(root, {"readme.md": b"# hi\n"})
142 monkeypatch.chdir(root)
143 _stage_dir(root, "mydir")
144
145 result = runner.invoke(None, ["code", "reset", "mydir/"], env=_env(root))
146
147 assert result.exit_code == 0
148 assert "directory" in result.output
149 assert "file" not in result.output
150
151
152 # ---------------------------------------------------------------------------
153 # RS-3 Unstage specific file: says 'file' (singular)
154 # ---------------------------------------------------------------------------
155
156 class TestResetSpecificFile:
157 def test_unstage_one_file_says_singular(
158 self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
159 ) -> None:
160 root = _init_repo(tmp_path)
161 _make_commit(root, {"readme.md": b"# hi\n"})
162 monkeypatch.chdir(root)
163 _stage_file(root, "new.py")
164
165 result = runner.invoke(None, ["code", "reset", "new.py"], env=_env(root))
166
167 assert result.exit_code == 0
168 assert "1 file" in result.output
169 assert "file(s)" not in result.output
170
171
172 # ---------------------------------------------------------------------------
173 # RS-4 Mixed unstage: all three kinds appear
174 # ---------------------------------------------------------------------------
175
176 class TestResetMixed:
177 def test_mixed_unstage_shows_dir_file_counts(
178 self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
179 ) -> None:
180 """Staging a dir + a file then resetting all must report both counts."""
181 root = _init_repo(tmp_path)
182 _make_commit(root, {"readme.md": b"# hi\n"})
183 monkeypatch.chdir(root)
184 _stage_dir(root, "mydir")
185 _stage_file(root, "new.py")
186
187 result = runner.invoke(None, ["code", "reset"], env=_env(root))
188
189 assert result.exit_code == 0
190 assert "directory" in result.output
191 assert "file" in result.output
192
193
194 # ---------------------------------------------------------------------------
195 # RS-5 Dry-run unstage all: uses granular format
196 # ---------------------------------------------------------------------------
197
198 class TestResetDryRun:
199 def test_dry_run_all_granular(
200 self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
201 ) -> None:
202 root = _init_repo(tmp_path)
203 _make_commit(root, {"readme.md": b"# hi\n"})
204 monkeypatch.chdir(root)
205 _stage_dir(root, "mydir")
206 _stage_file(root, "new.py")
207
208 result = runner.invoke(None, ["code", "reset", "-n"], env=_env(root))
209
210 assert result.exit_code == 0
211 assert "directory" in result.output
212 assert "file" in result.output
213 assert "file(s)" not in result.output
File History 2 commits
sha256:a154bc65916614c833d5a40a10d81ba3eae0d0495b0afddd34dc34f18d5e91b8 fix: test suite alignment and typing audit — zero violations Sonnet 4.6 minor 22 days ago
sha256:3767afb72520f9b56053bb98fd83d323f738ee4cad16e306e8cf6862608380e4 feat: first-class directory tracking across status, diff, r… Sonnet 4.6 minor 23 days ago