Python · 12828 bytes Raw Blame History
1 """`dlm show` CLI — pretty + --json, uninitialized-store path (Sprint 13)."""
2
3 from __future__ import annotations
4
5 import json
6 from datetime import datetime
7 from pathlib import Path
8
9 import pytest
10 from typer.testing import CliRunner
11
12 from dlm.cli.app import app
13
14
15 def _scaffold(tmp_path: Path) -> Path:
16 """Run `dlm init` to produce a valid .dlm file the show cmd can read."""
17 doc = tmp_path / "doc.dlm"
18 runner = CliRunner()
19 result = runner.invoke(app, ["init", str(doc), "--base", "smollm2-135m"])
20 assert result.exit_code == 0, result.output
21 return doc
22
23
24 class TestUninitializedStore:
25 """`dlm init` now creates the store + manifest (audit-05 B2), so a
26 truly uninitialized path means: the `.dlm` file exists but its
27 store directory is absent. Simulate by writing the `.dlm` by hand
28 and pointing DLM_HOME at an unrelated empty directory.
29 """
30
31 def _write_doc(self, path: Path, dlm_id: str) -> Path:
32 path.write_text(
33 f"---\ndlm_id: {dlm_id}\nbase_model: smollm2-135m\n---\nbody\n",
34 encoding="utf-8",
35 )
36 return path
37
38 def test_human_output_says_not_initialized(
39 self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
40 ) -> None:
41 monkeypatch.setenv("DLM_HOME", str(tmp_path / "fresh-home"))
42 # 26-char Crockford base32 ULID (no I / L / O / U).
43 doc = self._write_doc(tmp_path / "doc.dlm", "01HRSHWZ" + "0" * 18)
44 runner = CliRunner()
45 result = runner.invoke(app, ["show", str(doc)])
46 assert result.exit_code == 0, result.output
47 joined = result.output
48 assert "not yet initialized" in joined or "not initialized" in joined
49
50 def test_json_output_reports_not_initialized(
51 self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
52 ) -> None:
53 monkeypatch.setenv("DLM_HOME", str(tmp_path / "fresh-home"))
54 doc = self._write_doc(tmp_path / "doc.dlm", "01HRSHWJ" + "0" * 18)
55 runner = CliRunner()
56 result = runner.invoke(app, ["show", str(doc), "--json"])
57 assert result.exit_code == 0, result.output
58
59 parsed = json.loads(result.output)
60 assert parsed["store_initialized"] is False
61 assert parsed["base_model"] == "smollm2-135m"
62 assert "dlm_id" in parsed
63
64
65 class TestInitializedStore:
66 def test_human_output_renders_fields(
67 self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
68 ) -> None:
69 """Create a store manifest manually and ensure show renders it."""
70 from dlm.doc.parser import parse_file
71 from dlm.store.manifest import Manifest, save_manifest
72 from dlm.store.paths import for_dlm
73
74 home = tmp_path / "dlm-home"
75 monkeypatch.setenv("DLM_HOME", str(home))
76
77 doc = _scaffold(tmp_path)
78 parsed = parse_file(doc)
79 store = for_dlm(parsed.frontmatter.dlm_id)
80 store.ensure_layout()
81 save_manifest(
82 store.manifest,
83 Manifest(dlm_id=parsed.frontmatter.dlm_id, base_model="smollm2-135m"),
84 )
85
86 runner = CliRunner()
87 result = runner.invoke(app, ["show", str(doc)])
88 assert result.exit_code == 0, result.output
89 joined = result.output
90 assert parsed.frontmatter.dlm_id in joined
91 assert "smollm2-135m" in joined
92 assert "training runs" in joined
93
94 def test_json_schema_keys(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
95 """JSON contract: documented keys present; gate against accidental drift."""
96 from dlm.doc.parser import parse_file
97 from dlm.store.manifest import Manifest, save_manifest
98 from dlm.store.paths import for_dlm
99
100 home = tmp_path / "dlm-home"
101 monkeypatch.setenv("DLM_HOME", str(home))
102
103 doc = _scaffold(tmp_path)
104 parsed = parse_file(doc)
105 store = for_dlm(parsed.frontmatter.dlm_id)
106 store.ensure_layout()
107 save_manifest(
108 store.manifest,
109 Manifest(dlm_id=parsed.frontmatter.dlm_id, base_model="smollm2-135m"),
110 )
111
112 runner = CliRunner()
113 result = runner.invoke(app, ["show", str(doc), "--json"])
114 assert result.exit_code == 0, result.output
115
116 payload = json.loads(result.output)
117 expected_keys = {
118 "dlm_id",
119 "path",
120 "base_model",
121 "base_model_revision",
122 "adapter_version",
123 "training_runs",
124 "last_trained_at",
125 "has_adapter_current",
126 "replay_size_bytes",
127 "total_size_bytes",
128 "source_path",
129 "orphaned",
130 "exports",
131 "content_hashes",
132 "pinned_versions",
133 }
134 assert expected_keys.issubset(payload.keys())
135
136 def test_json_exports_surface_target_names(
137 self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
138 ) -> None:
139 from dlm.doc.parser import parse_file
140 from dlm.store.manifest import ExportSummary, Manifest, save_manifest
141 from dlm.store.paths import for_dlm
142
143 home = tmp_path / "dlm-home"
144 monkeypatch.setenv("DLM_HOME", str(home))
145
146 doc = _scaffold(tmp_path)
147 parsed = parse_file(doc)
148 store = for_dlm(parsed.frontmatter.dlm_id)
149 store.ensure_layout()
150 save_manifest(
151 store.manifest,
152 Manifest(
153 dlm_id=parsed.frontmatter.dlm_id,
154 base_model="smollm2-135m",
155 exports=[
156 ExportSummary(
157 exported_at=datetime(2026, 4, 23, 12, 0, 0),
158 target="ollama",
159 quant="Q4_K_M",
160 merged=False,
161 ollama_name="doc:latest",
162 ),
163 ExportSummary(
164 exported_at=datetime(2026, 4, 23, 12, 5, 0),
165 target="llama-server",
166 quant="Q4_K_M",
167 merged=False,
168 ),
169 ExportSummary(
170 exported_at=datetime(2026, 4, 23, 12, 10, 0),
171 target="mlx-serve",
172 quant="hf",
173 merged=False,
174 ),
175 ],
176 ),
177 )
178
179 runner = CliRunner()
180 result = runner.invoke(app, ["show", str(doc), "--json"])
181 assert result.exit_code == 0, result.output
182
183 payload = json.loads(result.output)
184 assert [export["target"] for export in payload["exports"]] == [
185 "ollama",
186 "llama-server",
187 "mlx-serve",
188 ]
189
190 def test_json_surfaces_latest_preference_mining_summary(
191 self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
192 ) -> None:
193 from dlm.doc.parser import parse_file
194 from dlm.metrics import MetricsRecorder, PreferenceMineEvent
195 from dlm.store.manifest import Manifest, save_manifest
196 from dlm.store.paths import for_dlm
197
198 home = tmp_path / "dlm-home"
199 monkeypatch.setenv("DLM_HOME", str(home))
200
201 doc = _scaffold(tmp_path)
202 parsed = parse_file(doc)
203 store = for_dlm(parsed.frontmatter.dlm_id)
204 store.ensure_layout()
205 save_manifest(
206 store.manifest,
207 Manifest(dlm_id=parsed.frontmatter.dlm_id, base_model="smollm2-135m"),
208 )
209 recorder = MetricsRecorder(store.root)
210 recorder.record_preference_mine(
211 PreferenceMineEvent(
212 run_id=7,
213 judge_name="sway",
214 sample_count=4,
215 mined_pairs=2,
216 skipped_prompts=1,
217 write_mode="applied",
218 )
219 )
220
221 runner = CliRunner()
222 result = runner.invoke(app, ["show", str(doc), "--json"])
223 assert result.exit_code == 0, result.output
224
225 payload = json.loads(result.output)
226 pref = payload["preference_mining"]
227 assert pref["last_run_id"] == 7
228 assert pref["run_count"] == 1
229 assert pref["event_count"] == 1
230 assert pref["last_run_event_count"] == 1
231 assert pref["total_mined_pairs"] == 2
232 assert pref["total_skipped_prompts"] == 1
233 assert pref["last_event"]["judge_name"] == "sway"
234 assert pref["last_event"]["write_mode"] == "applied"
235 assert payload["preference_mining_runs"] == 1
236 assert payload["total_auto_mined_pairs"] == 2
237
238
239 class TestTrainingSources:
240 """`training.sources` directives surface in `dlm show --json` output."""
241
242 def _write_doc_with_sources(self, tmp_path: Path) -> Path:
243 src = tmp_path / "src"
244 src.mkdir()
245 (src / "a.py").write_text("print(1)\n")
246 (src / "b.py").write_text("print(2)\n")
247 doc = tmp_path / "doc.dlm"
248 doc.write_text(
249 "---\n"
250 "dlm_id: 01HRSHWA" + "0" * 18 + "\n"
251 "dlm_version: 6\n"
252 "base_model: smollm2-135m\n"
253 "training:\n"
254 " sources:\n"
255 " - path: src\n"
256 " include: ['**/*.py']\n"
257 "---\n"
258 "body\n",
259 encoding="utf-8",
260 )
261 return doc
262
263 def test_json_reports_training_sources(
264 self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
265 ) -> None:
266 monkeypatch.setenv("DLM_HOME", str(tmp_path / "fresh-home"))
267 doc = self._write_doc_with_sources(tmp_path)
268 runner = CliRunner()
269 result = runner.invoke(app, ["show", str(doc), "--json"])
270 assert result.exit_code == 0, result.output
271 payload = json.loads(result.output)
272 assert "training_sources" in payload
273 sources = payload["training_sources"]
274 assert len(sources) == 1
275 assert sources[0]["path"] == "src"
276 assert sources[0]["file_count"] == 2
277
278 def test_json_reports_discovered_training_configs(
279 self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
280 ) -> None:
281 """Sprint 30: `.dlm/training.yaml` + `.dlm/ignore` under the source
282 tree surface as `discovered_training_configs` in the JSON output."""
283 monkeypatch.setenv("DLM_HOME", str(tmp_path / "fresh-home"))
284 src = tmp_path / "src"
285 src.mkdir()
286 (src / "a.py").write_text("print(1)\n")
287 # Drop a .dlm/training.yaml + .dlm/ignore in the source tree
288 (src / ".dlm").mkdir()
289 (src / ".dlm" / "training.yaml").write_text(
290 "dlm_training_version: 1\nmetadata:\n language: python\n",
291 encoding="utf-8",
292 )
293 (src / ".dlm" / "ignore").write_text("*.log\n", encoding="utf-8")
294 doc = tmp_path / "doc.dlm"
295 doc.write_text(
296 "---\n"
297 "dlm_id: 01HRSHWE" + "0" * 18 + "\n"
298 "dlm_version: 6\n"
299 "base_model: smollm2-135m\n"
300 "training:\n"
301 " sources:\n"
302 " - path: src\n"
303 " include: ['**/*.py']\n"
304 "---\n"
305 "body\n",
306 encoding="utf-8",
307 )
308 runner = CliRunner()
309 result = runner.invoke(app, ["show", str(doc), "--json"])
310 assert result.exit_code == 0, result.output
311 payload = json.loads(result.output)
312 assert "discovered_training_configs" in payload
313 configs = payload["discovered_training_configs"]
314 assert len(configs) == 1
315 assert configs[0]["has_training_yaml"] is True
316 assert configs[0]["has_ignore"] is True
317 assert configs[0]["metadata"] == {"language": "python"}
318 assert configs[0]["ignore_rules"] == 1
319
320 def test_human_output_lists_sources(
321 self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
322 ) -> None:
323 monkeypatch.setenv("DLM_HOME", str(tmp_path / "fresh-home"))
324 doc = self._write_doc_with_sources(tmp_path)
325 # Populate store to get through to the inspection path.
326 from dlm.doc.parser import parse_file
327 from dlm.store.manifest import Manifest, save_manifest
328 from dlm.store.paths import for_dlm
329
330 parsed = parse_file(doc)
331 store = for_dlm(parsed.frontmatter.dlm_id)
332 store.ensure_layout()
333 save_manifest(
334 store.manifest,
335 Manifest(dlm_id=parsed.frontmatter.dlm_id, base_model="smollm2-135m"),
336 )
337
338 runner = CliRunner()
339 result = runner.invoke(app, ["show", str(doc)])
340 assert result.exit_code == 0, result.output
341 assert "training sources" in result.output
342 assert "src" in result.output
343
344
345 class TestBadInput:
346 def test_missing_file_exits_nonzero(self, tmp_path: Path) -> None:
347 runner = CliRunner()
348 result = runner.invoke(app, ["show", str(tmp_path / "does-not-exist.dlm")])
349 assert result.exit_code != 0