gabriel / muse public

test_force_track.py file-level

at sha256:2 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:4 Merge branch 'dev' into main · gabriel · Jun 17, 2026
1 """TDD: [force_track] whitelist in .museignore overrides the built-in secrets blocklist.
2
3 Force-track lets a repo explicitly commit files that would otherwise be blocked
4 by the engine-level secrets blocklist (*.key, *.pem, .env, …). The canonical
5 use-case is dev infrastructure: a self-signed localhost TLS cert/key that lives
6 in the repo so it survives clean pulls.
7
8 Design constraints tested here:
9 - exact paths only in [force_track].paths β€” no globs (deliberate, unambiguous)
10 - overrides BOTH the built-in secrets blocklist AND user .museignore patterns
11 - parse errors are handled gracefully; absent section β†’ empty whitelist
12 - walk_workdir / walk_workdir_with_dirs both respect the whitelist
13 """
14
15 from __future__ import annotations
16
17 import pathlib
18
19 import pytest
20
21 from muse.core.ignore import (
22 MuseIgnoreConfig,
23 load_force_track_paths,
24 load_ignore_config,
25 )
26 from muse.core.snapshot import _BUILTIN_SECRET_PATTERNS, walk_workdir
27 from muse.core.paths import muse_dir, repo_json_path
28
29
30 # ---------------------------------------------------------------------------
31 # load_ignore_config β€” [force_track] parsing
32 # ---------------------------------------------------------------------------
33
34
35 class TestLoadIgnoreConfigForceTrack:
36 def test_missing_section_returns_no_force_track_key(
37 self, tmp_path: pathlib.Path
38 ) -> None:
39 (tmp_path / ".museignore").write_text('[global]\npatterns = ["*.tmp"]\n')
40 config = load_ignore_config(tmp_path)
41 assert "force_track" not in config
42
43 def test_force_track_section_parsed(self, tmp_path: pathlib.Path) -> None:
44 (tmp_path / ".museignore").write_text(
45 "[force_track]\npaths = [\"deploy/local-tls/localhost.key\"]\n"
46 )
47 config = load_ignore_config(tmp_path)
48 assert config.get("force_track", {}).get("paths") == [
49 "deploy/local-tls/localhost.key"
50 ]
51
52 def test_force_track_multiple_paths(self, tmp_path: pathlib.Path) -> None:
53 (tmp_path / ".museignore").write_text(
54 "[force_track]\npaths = [\n"
55 " \"deploy/local-tls/localhost.key\",\n"
56 " \"deploy/local-tls/localhost.crt\",\n"
57 "]\n"
58 )
59 config = load_ignore_config(tmp_path)
60 paths = config.get("force_track", {}).get("paths", [])
61 assert "deploy/local-tls/localhost.key" in paths
62 assert "deploy/local-tls/localhost.crt" in paths
63 assert len(paths) == 2
64
65 def test_force_track_empty_paths_list(self, tmp_path: pathlib.Path) -> None:
66 (tmp_path / ".museignore").write_text("[force_track]\npaths = []\n")
67 config = load_ignore_config(tmp_path)
68 assert config.get("force_track", {}).get("paths") == []
69
70 def test_force_track_missing_paths_key(self, tmp_path: pathlib.Path) -> None:
71 (tmp_path / ".museignore").write_text("[force_track]\n")
72 config = load_ignore_config(tmp_path)
73 assert config.get("force_track", {}).get("paths") is None
74
75 def test_force_track_non_string_paths_silently_dropped(
76 self, tmp_path: pathlib.Path
77 ) -> None:
78 (tmp_path / ".museignore").write_text(
79 '[force_track]\npaths = ["good/path.key", 42, true, "other.pem"]\n'
80 )
81 config = load_ignore_config(tmp_path)
82 paths = config.get("force_track", {}).get("paths", [])
83 assert paths == ["good/path.key", "other.pem"]
84
85 def test_force_track_coexists_with_global_and_domain(
86 self, tmp_path: pathlib.Path
87 ) -> None:
88 (tmp_path / ".museignore").write_text(
89 '[global]\npatterns = ["*.tmp"]\n'
90 '[domain.code]\npatterns = ["build/"]\n'
91 '[force_track]\npaths = ["infra/dev.key"]\n'
92 )
93 config = load_ignore_config(tmp_path)
94 assert config.get("global", {}).get("patterns") == ["*.tmp"]
95 assert config.get("domain", {}).get("code", {}).get("patterns") == ["build/"]
96 assert config.get("force_track", {}).get("paths") == ["infra/dev.key"]
97
98 def test_no_museignore_returns_empty_config(self, tmp_path: pathlib.Path) -> None:
99 config = load_ignore_config(tmp_path)
100 assert config == {}
101
102
103 # ---------------------------------------------------------------------------
104 # load_force_track_paths β€” convenience helper
105 # ---------------------------------------------------------------------------
106
107
108 class TestLoadForceTrackPaths:
109 def test_no_museignore_returns_empty_frozenset(
110 self, tmp_path: pathlib.Path
111 ) -> None:
112 result = load_force_track_paths(tmp_path)
113 assert result == frozenset()
114
115 def test_no_force_track_section_returns_empty(
116 self, tmp_path: pathlib.Path
117 ) -> None:
118 (tmp_path / ".museignore").write_text('[global]\npatterns = ["*.tmp"]\n')
119 result = load_force_track_paths(tmp_path)
120 assert result == frozenset()
121
122 def test_returns_frozenset_of_paths(self, tmp_path: pathlib.Path) -> None:
123 (tmp_path / ".museignore").write_text(
124 '[force_track]\npaths = ["deploy/tls/dev.key", "deploy/tls/dev.crt"]\n'
125 )
126 result = load_force_track_paths(tmp_path)
127 assert isinstance(result, frozenset)
128 assert result == frozenset({"deploy/tls/dev.key", "deploy/tls/dev.crt"})
129
130 def test_empty_paths_returns_empty_frozenset(
131 self, tmp_path: pathlib.Path
132 ) -> None:
133 (tmp_path / ".museignore").write_text("[force_track]\npaths = []\n")
134 result = load_force_track_paths(tmp_path)
135 assert result == frozenset()
136
137 def test_paths_use_posix_separators(self, tmp_path: pathlib.Path) -> None:
138 (tmp_path / ".museignore").write_text(
139 '[force_track]\npaths = ["deploy/local-tls/localhost.key"]\n'
140 )
141 result = load_force_track_paths(tmp_path)
142 assert "deploy/local-tls/localhost.key" in result
143
144
145 # ---------------------------------------------------------------------------
146 # Integration: walk_workdir respects [force_track]
147 # ---------------------------------------------------------------------------
148
149
150 def _init_code_repo(root: pathlib.Path) -> None:
151 """Minimal .muse/repo.json so load_ignore_patterns detects domain=code."""
152 muse_dir(root).mkdir(exist_ok=True)
153 (repo_json_path(root)).write_text('{"repo_id": "x", "domain": "code"}')
154
155
156 class TestForceTrackWalkWorkdir:
157 def test_key_file_excluded_without_force_track(
158 self, tmp_path: pathlib.Path
159 ) -> None:
160 _init_code_repo(tmp_path)
161 (tmp_path / "deploy").mkdir()
162 (tmp_path / "deploy" / "server.key").write_bytes(b"private key bytes")
163 (tmp_path / "app.py").write_text("x = 1\n")
164
165 manifest = walk_workdir(tmp_path)
166 assert "deploy/server.key" not in manifest
167 assert "app.py" in manifest
168
169 def test_key_file_included_when_force_tracked(
170 self, tmp_path: pathlib.Path
171 ) -> None:
172 _init_code_repo(tmp_path)
173 (tmp_path / "deploy").mkdir()
174 (tmp_path / "deploy" / "server.key").write_bytes(b"private key bytes")
175 (tmp_path / "app.py").write_text("x = 1\n")
176 (tmp_path / ".museignore").write_text(
177 '[force_track]\npaths = ["deploy/server.key"]\n'
178 )
179
180 manifest = walk_workdir(tmp_path)
181 assert "deploy/server.key" in manifest
182 assert "app.py" in manifest
183
184 def test_pem_file_included_when_force_tracked(
185 self, tmp_path: pathlib.Path
186 ) -> None:
187 _init_code_repo(tmp_path)
188 (tmp_path / "infra").mkdir()
189 (tmp_path / "infra" / "ca.pem").write_bytes(b"cert chain")
190 (tmp_path / ".museignore").write_text(
191 '[force_track]\npaths = ["infra/ca.pem"]\n'
192 )
193
194 manifest = walk_workdir(tmp_path)
195 assert "infra/ca.pem" in manifest
196
197 def test_env_file_included_when_force_tracked(
198 self, tmp_path: pathlib.Path
199 ) -> None:
200 _init_code_repo(tmp_path)
201 (tmp_path / ".env").write_text("DEV_ONLY=true\n")
202 (tmp_path / ".museignore").write_text(
203 '[force_track]\npaths = [".env"]\n'
204 )
205
206 manifest = walk_workdir(tmp_path)
207 assert ".env" in manifest
208
209 def test_non_force_tracked_secret_still_blocked(
210 self, tmp_path: pathlib.Path
211 ) -> None:
212 _init_code_repo(tmp_path)
213 (tmp_path / "other.key").write_bytes(b"another key")
214 (tmp_path / "deploy").mkdir()
215 (tmp_path / "deploy" / "server.key").write_bytes(b"dev key")
216 (tmp_path / ".museignore").write_text(
217 '[force_track]\npaths = ["deploy/server.key"]\n'
218 )
219
220 manifest = walk_workdir(tmp_path)
221 assert "deploy/server.key" in manifest
222 assert "other.key" not in manifest # not whitelisted
223
224 def test_force_track_overrides_user_ignore_patterns(
225 self, tmp_path: pathlib.Path
226 ) -> None:
227 _init_code_repo(tmp_path)
228 (tmp_path / "config").mkdir()
229 (tmp_path / "config" / "local.cfg").write_text("debug=true\n")
230 (tmp_path / ".museignore").write_text(
231 '[global]\npatterns = ["config/"]\n'
232 '[force_track]\npaths = ["config/local.cfg"]\n'
233 )
234
235 manifest = walk_workdir(tmp_path)
236 assert "config/local.cfg" in manifest
237
238 def test_force_track_exact_path_only_no_glob_expansion(
239 self, tmp_path: pathlib.Path
240 ) -> None:
241 _init_code_repo(tmp_path)
242 (tmp_path / "a.key").write_bytes(b"key a")
243 (tmp_path / "b.key").write_bytes(b"key b")
244 # Listing "*.key" in force_track should NOT glob-expand β€” it's an exact path.
245 (tmp_path / ".museignore").write_text(
246 '[force_track]\npaths = ["*.key"]\n'
247 )
248
249 manifest = walk_workdir(tmp_path)
250 # "*.key" is not a real filename so neither file matches the exact path.
251 assert "a.key" not in manifest
252 assert "b.key" not in manifest
253
254 def test_force_track_nonexistent_path_no_error(
255 self, tmp_path: pathlib.Path
256 ) -> None:
257 _init_code_repo(tmp_path)
258 (tmp_path / "app.py").write_text("x = 1\n")
259 (tmp_path / ".museignore").write_text(
260 '[force_track]\npaths = ["ghost.key"]\n'
261 )
262
263 manifest = walk_workdir(tmp_path)
264 assert "app.py" in manifest # normal files still tracked
265
266 def test_localhost_tls_use_case(self, tmp_path: pathlib.Path) -> None:
267 """The canonical use-case: dev TLS cert+key committed to the repo."""
268 _init_code_repo(tmp_path)
269 tls = tmp_path / "deploy" / "local-tls"
270 tls.mkdir(parents=True)
271 (tls / "localhost.crt").write_bytes(b"cert bytes")
272 (tls / "localhost.key").write_bytes(b"private key bytes")
273 (tmp_path / "app.py").write_text("x = 1\n")
274 (tmp_path / ".museignore").write_text(
275 "[force_track]\npaths = [\n"
276 " \"deploy/local-tls/localhost.crt\",\n"
277 " \"deploy/local-tls/localhost.key\",\n"
278 "]\n"
279 )
280
281 manifest = walk_workdir(tmp_path)
282 assert "deploy/local-tls/localhost.crt" in manifest
283 assert "deploy/local-tls/localhost.key" in manifest
284 assert "app.py" in manifest
285
286 def test_force_track_does_not_affect_unrelated_secrets(
287 self, tmp_path: pathlib.Path
288 ) -> None:
289 _init_code_repo(tmp_path)
290 (tmp_path / ".env").write_text("SECRET=hunter2\n")
291 (tmp_path / "deploy").mkdir()
292 (tmp_path / "deploy" / "localhost.key").write_bytes(b"key")
293 # Only localhost.key is whitelisted β€” .env must still be blocked.
294 (tmp_path / ".museignore").write_text(
295 '[force_track]\npaths = ["deploy/localhost.key"]\n'
296 )
297
298 manifest = walk_workdir(tmp_path)
299 assert "deploy/localhost.key" in manifest
300 assert ".env" not in manifest