woolly 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {woolly-0.3.0 → woolly-0.4.0}/PKG-INFO +3 -1
  2. woolly-0.4.0/examples/template/example_template.md +16 -0
  3. {woolly-0.3.0 → woolly-0.4.0}/pyproject.toml +5 -0
  4. {woolly-0.3.0 → woolly-0.4.0}/tests/functional/test_cli.py +83 -0
  5. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_commands/test_check.py +120 -0
  6. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_optional_dependencies.py +3 -0
  7. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_reporters/test_json.py +51 -0
  8. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_reporters/test_markdown.py +65 -0
  9. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_reporters/test_stdout.py +29 -0
  10. woolly-0.4.0/tests/unit/test_reporters/test_template.py +358 -0
  11. {woolly-0.3.0 → woolly-0.4.0}/uv.lock +104 -0
  12. {woolly-0.3.0 → woolly-0.4.0}/woolly/commands/check.py +69 -1
  13. {woolly-0.3.0 → woolly-0.4.0}/woolly/reporters/__init__.py +15 -1
  14. {woolly-0.3.0 → woolly-0.4.0}/woolly/reporters/base.py +3 -0
  15. {woolly-0.3.0 → woolly-0.4.0}/woolly/reporters/json.py +6 -1
  16. {woolly-0.3.0 → woolly-0.4.0}/woolly/reporters/markdown.py +12 -9
  17. {woolly-0.3.0 → woolly-0.4.0}/woolly/reporters/stdout.py +5 -4
  18. woolly-0.4.0/woolly/reporters/template.py +215 -0
  19. {woolly-0.3.0 → woolly-0.4.0}/.coverage +0 -0
  20. {woolly-0.3.0 → woolly-0.4.0}/.github/workflows/lint.yml +0 -0
  21. {woolly-0.3.0 → woolly-0.4.0}/.github/workflows/publish.yml +0 -0
  22. {woolly-0.3.0 → woolly-0.4.0}/.github/workflows/tests.yml +0 -0
  23. {woolly-0.3.0 → woolly-0.4.0}/.gitignore +0 -0
  24. {woolly-0.3.0 → woolly-0.4.0}/.python-version +0 -0
  25. {woolly-0.3.0 → woolly-0.4.0}/LICENSE +0 -0
  26. {woolly-0.3.0 → woolly-0.4.0}/README.md +0 -0
  27. {woolly-0.3.0 → woolly-0.4.0}/tests/__init__.py +0 -0
  28. {woolly-0.3.0 → woolly-0.4.0}/tests/conftest.py +0 -0
  29. {woolly-0.3.0 → woolly-0.4.0}/tests/functional/__init__.py +0 -0
  30. {woolly-0.3.0 → woolly-0.4.0}/tests/functional/test_optional_flag.py +0 -0
  31. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/__init__.py +0 -0
  32. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_cache.py +0 -0
  33. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_commands/__init__.py +0 -0
  34. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_commands/test_other_commands.py +0 -0
  35. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_debug.py +0 -0
  36. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_languages/__init__.py +0 -0
  37. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_languages/test_base.py +0 -0
  38. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_languages/test_python.py +0 -0
  39. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_languages/test_registry.py +0 -0
  40. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_languages/test_rust.py +0 -0
  41. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_progress.py +0 -0
  42. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_reporters/__init__.py +0 -0
  43. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_reporters/test_base.py +0 -0
  44. {woolly-0.3.0 → woolly-0.4.0}/tests/unit/test_reporters/test_registry.py +0 -0
  45. {woolly-0.3.0 → woolly-0.4.0}/woolly/__init__.py +0 -0
  46. {woolly-0.3.0 → woolly-0.4.0}/woolly/__main__.py +0 -0
  47. {woolly-0.3.0 → woolly-0.4.0}/woolly/cache.py +0 -0
  48. {woolly-0.3.0 → woolly-0.4.0}/woolly/commands/__init__.py +0 -0
  49. {woolly-0.3.0 → woolly-0.4.0}/woolly/commands/clear_cache.py +0 -0
  50. {woolly-0.3.0 → woolly-0.4.0}/woolly/commands/list_formats.py +0 -0
  51. {woolly-0.3.0 → woolly-0.4.0}/woolly/commands/list_languages.py +0 -0
  52. {woolly-0.3.0 → woolly-0.4.0}/woolly/debug.py +0 -0
  53. {woolly-0.3.0 → woolly-0.4.0}/woolly/http.py +0 -0
  54. {woolly-0.3.0 → woolly-0.4.0}/woolly/languages/__init__.py +0 -0
  55. {woolly-0.3.0 → woolly-0.4.0}/woolly/languages/base.py +0 -0
  56. {woolly-0.3.0 → woolly-0.4.0}/woolly/languages/python.py +0 -0
  57. {woolly-0.3.0 → woolly-0.4.0}/woolly/languages/rust.py +0 -0
  58. {woolly-0.3.0 → woolly-0.4.0}/woolly/progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: woolly
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Check if package dependencies are available in Fedora. Supports Rust, Python, and more.
5
5
  Author-email: Rodolfo Olivieri <rodolfo.olivieri3@gmail.com>
6
6
  License-File: LICENSE
@@ -26,6 +26,8 @@ Requires-Dist: cyclopts>=4.3.0
26
26
  Requires-Dist: httpx>=0.28.0
27
27
  Requires-Dist: pydantic>=2.10.0
28
28
  Requires-Dist: rich>=14.2.0
29
+ Provides-Extra: template
30
+ Requires-Dist: jinja2>=3.1.0; extra == 'template'
29
31
  Description-Content-Type: text/markdown
30
32
 
31
33
  # 🐑 Woolly
@@ -0,0 +1,16 @@
1
+ Report for {{ root_package }}
2
+
3
+ Generated: {{ timestamp }}
4
+ Language: {{ language }}
5
+
6
+ ## Summary
7
+ - Total: {{ total_dependencies }}
8
+ - Packaged: {{ packaged_count }}
9
+ - Missing: {{ missing_count }}
10
+
11
+ {% if missing_packages %}
12
+ ## Missing Packages
13
+ {% for pkg in missing_packages %}
14
+ - {{ pkg }}
15
+ {% endfor %}
16
+ {% endif %}
@@ -37,6 +37,11 @@ classifiers = [
37
37
  "Topic :: Utilities",
38
38
  ]
39
39
 
40
+ [project.optional-dependencies]
41
+ template = [
42
+ "jinja2>=3.1.0",
43
+ ]
44
+
40
45
  [project.url]
41
46
  "Source code" = "https://github.com/r0x0d/woolly"
42
47
  "Bug Tracker" = "https://github.com/r0x0d/woolly/issues"
@@ -34,6 +34,7 @@ class TestCliHelp:
34
34
  assert result.returncode == 0
35
35
  assert "package" in result.stdout.lower()
36
36
  assert "--lang" in result.stdout
37
+ assert "--missing-only" in result.stdout
37
38
 
38
39
 
39
40
  class TestListLanguages:
@@ -263,6 +264,32 @@ class TestCheckCommandPython:
263
264
  class TestCheckCommandOptions:
264
265
  """Tests for check command options."""
265
266
 
267
+ @pytest.mark.functional
268
+ @pytest.mark.slow
269
+ def test_missing_only_option(self, cli_runner, tmp_path, monkeypatch):
270
+ """Good path: missing-only option is accepted."""
271
+ monkeypatch.setenv("HOME", str(tmp_path))
272
+
273
+ result = cli_runner(
274
+ "check", "cfg-if", "--lang", "rust", "--missing-only", "--no-progress"
275
+ )
276
+
277
+ assert result.returncode == 0
278
+ # Should not show the dependency tree when --missing-only is used
279
+ assert "Dependency Tree:" not in result.stdout
280
+
281
+ @pytest.mark.functional
282
+ @pytest.mark.slow
283
+ def test_missing_only_short_option(self, cli_runner, tmp_path, monkeypatch):
284
+ """Good path: missing-only short option (-m) is accepted."""
285
+ monkeypatch.setenv("HOME", str(tmp_path))
286
+
287
+ result = cli_runner("check", "cfg-if", "--lang", "rust", "-m", "--no-progress")
288
+
289
+ assert result.returncode == 0
290
+ # Should not show the dependency tree when -m is used
291
+ assert "Dependency Tree:" not in result.stdout
292
+
266
293
  @pytest.mark.functional
267
294
  @pytest.mark.slow
268
295
  def test_max_depth_option(self, cli_runner, tmp_path, monkeypatch):
@@ -275,6 +302,62 @@ class TestCheckCommandOptions:
275
302
 
276
303
  assert result.returncode == 0
277
304
 
305
+ @pytest.mark.functional
306
+ @pytest.mark.slow
307
+ def test_exclude_option(self, cli_runner, tmp_path, monkeypatch):
308
+ """Good path: exclude option is accepted and displayed."""
309
+ monkeypatch.setenv("HOME", str(tmp_path))
310
+
311
+ result = cli_runner(
312
+ "check",
313
+ "cfg-if",
314
+ "--lang",
315
+ "rust",
316
+ "--exclude",
317
+ "windows*",
318
+ "--no-progress",
319
+ )
320
+
321
+ assert result.returncode == 0
322
+ assert "Excluding dependencies matching" in result.stdout
323
+ assert "windows*" in result.stdout
324
+
325
+ @pytest.mark.functional
326
+ @pytest.mark.slow
327
+ def test_exclude_multiple_patterns(self, cli_runner, tmp_path, monkeypatch):
328
+ """Good path: multiple exclude patterns are accepted."""
329
+ monkeypatch.setenv("HOME", str(tmp_path))
330
+
331
+ result = cli_runner(
332
+ "check",
333
+ "cfg-if",
334
+ "--lang",
335
+ "rust",
336
+ "--exclude",
337
+ "windows*",
338
+ "--exclude",
339
+ "*-sys",
340
+ "--no-progress",
341
+ )
342
+
343
+ assert result.returncode == 0
344
+ assert "Excluding dependencies matching" in result.stdout
345
+ assert "windows*" in result.stdout
346
+ assert "*-sys" in result.stdout
347
+
348
+ @pytest.mark.functional
349
+ @pytest.mark.slow
350
+ def test_exclude_short_option(self, cli_runner, tmp_path, monkeypatch):
351
+ """Good path: -e short option works."""
352
+ monkeypatch.setenv("HOME", str(tmp_path))
353
+
354
+ result = cli_runner(
355
+ "check", "cfg-if", "--lang", "rust", "-e", "win*", "--no-progress"
356
+ )
357
+
358
+ assert result.returncode == 0
359
+ assert "Excluding dependencies matching" in result.stdout
360
+
278
361
  @pytest.mark.functional
279
362
  @pytest.mark.slow
280
363
  def test_debug_option(self, cli_runner, tmp_path, monkeypatch):
@@ -170,6 +170,126 @@ class TestBuildTree:
170
170
 
171
171
  tracker.update.assert_called()
172
172
 
173
+ @pytest.mark.unit
174
+ def test_excludes_dependencies_matching_pattern(self, provider):
175
+ """Good path: excludes dependencies matching glob patterns."""
176
+ # Set up package with dependencies
177
+ provider.packages["parent"] = PackageInfo(name="parent", latest_version="1.0.0")
178
+ provider.packages["child"] = PackageInfo(name="child", latest_version="2.0.0")
179
+ provider.packages["windows-sys"] = PackageInfo(
180
+ name="windows-sys", latest_version="0.52.0"
181
+ )
182
+ provider.dependencies["parent:1.0.0"] = [
183
+ Dependency(name="child", version_requirement="^2.0", kind="normal"),
184
+ Dependency(name="windows-sys", version_requirement="^0.52", kind="normal"),
185
+ ]
186
+ provider.fedora_status["parent"] = FedoraPackageStatus(
187
+ is_packaged=True, versions=["1.0.0"]
188
+ )
189
+ provider.fedora_status["child"] = FedoraPackageStatus(
190
+ is_packaged=True, versions=["2.0.0"]
191
+ )
192
+ provider.fedora_status["windows-sys"] = FedoraPackageStatus(is_packaged=False)
193
+
194
+ tree = build_tree(provider, "parent", exclude_patterns=["windows*"])
195
+
196
+ # Should only have child, not windows-sys
197
+ assert isinstance(tree, Tree)
198
+ assert len(tree.children) == 1
199
+ child_label = str(tree.children[0].label)
200
+ assert "child" in child_label
201
+ # windows-sys should be excluded
202
+ for child in tree.children:
203
+ label = str(child.label) if hasattr(child, "label") else str(child)
204
+ assert "windows" not in label
205
+
206
+ @pytest.mark.unit
207
+ def test_excludes_multiple_patterns(self, provider):
208
+ """Good path: excludes dependencies matching multiple glob patterns."""
209
+ provider.packages["parent"] = PackageInfo(name="parent", latest_version="1.0.0")
210
+ provider.packages["good-dep"] = PackageInfo(
211
+ name="good-dep", latest_version="1.0.0"
212
+ )
213
+ provider.packages["win-dep"] = PackageInfo(
214
+ name="win-dep", latest_version="1.0.0"
215
+ )
216
+ provider.packages["macos-dep"] = PackageInfo(
217
+ name="macos-dep", latest_version="1.0.0"
218
+ )
219
+ provider.dependencies["parent:1.0.0"] = [
220
+ Dependency(name="good-dep", version_requirement="^1.0", kind="normal"),
221
+ Dependency(name="win-dep", version_requirement="^1.0", kind="normal"),
222
+ Dependency(name="macos-dep", version_requirement="^1.0", kind="normal"),
223
+ ]
224
+ provider.fedora_status["parent"] = FedoraPackageStatus(
225
+ is_packaged=True, versions=["1.0.0"]
226
+ )
227
+ provider.fedora_status["good-dep"] = FedoraPackageStatus(
228
+ is_packaged=True, versions=["1.0.0"]
229
+ )
230
+ provider.fedora_status["win-dep"] = FedoraPackageStatus(is_packaged=False)
231
+ provider.fedora_status["macos-dep"] = FedoraPackageStatus(is_packaged=False)
232
+
233
+ tree = build_tree(provider, "parent", exclude_patterns=["win*", "macos*"])
234
+
235
+ # Should only have good-dep
236
+ assert isinstance(tree, Tree)
237
+ assert len(tree.children) == 1
238
+ child_label = str(tree.children[0].label)
239
+ assert "good-dep" in child_label
240
+
241
+ @pytest.mark.unit
242
+ def test_exclude_patterns_applied_recursively(self, provider):
243
+ """Good path: exclude patterns are applied at all depth levels."""
244
+ provider.packages["root"] = PackageInfo(name="root", latest_version="1.0.0")
245
+ provider.packages["child"] = PackageInfo(name="child", latest_version="1.0.0")
246
+ provider.packages["windows-inner"] = PackageInfo(
247
+ name="windows-inner", latest_version="1.0.0"
248
+ )
249
+ provider.dependencies["root:1.0.0"] = [
250
+ Dependency(name="child", version_requirement="^1.0", kind="normal"),
251
+ ]
252
+ provider.dependencies["child:1.0.0"] = [
253
+ Dependency(name="windows-inner", version_requirement="^1.0", kind="normal"),
254
+ ]
255
+ provider.fedora_status["root"] = FedoraPackageStatus(
256
+ is_packaged=True, versions=["1.0.0"]
257
+ )
258
+ provider.fedora_status["child"] = FedoraPackageStatus(
259
+ is_packaged=True, versions=["1.0.0"]
260
+ )
261
+ provider.fedora_status["windows-inner"] = FedoraPackageStatus(is_packaged=False)
262
+
263
+ tree = build_tree(provider, "root", exclude_patterns=["windows*"])
264
+
265
+ # root -> child (no windows-inner)
266
+ assert isinstance(tree, Tree)
267
+ assert len(tree.children) == 1
268
+ child_tree = tree.children[0]
269
+ assert isinstance(child_tree, Tree)
270
+ # child should have no children (windows-inner was filtered)
271
+ assert len(child_tree.children) == 0
272
+
273
+ @pytest.mark.unit
274
+ def test_no_exclusion_when_patterns_none(self, provider):
275
+ """Good path: no exclusion when patterns is None."""
276
+ provider.packages["parent"] = PackageInfo(name="parent", latest_version="1.0.0")
277
+ provider.packages["child"] = PackageInfo(name="child", latest_version="1.0.0")
278
+ provider.dependencies["parent:1.0.0"] = [
279
+ Dependency(name="child", version_requirement="^1.0", kind="normal"),
280
+ ]
281
+ provider.fedora_status["parent"] = FedoraPackageStatus(
282
+ is_packaged=True, versions=["1.0.0"]
283
+ )
284
+ provider.fedora_status["child"] = FedoraPackageStatus(
285
+ is_packaged=True, versions=["1.0.0"]
286
+ )
287
+
288
+ tree = build_tree(provider, "parent", exclude_patterns=None)
289
+
290
+ assert isinstance(tree, Tree)
291
+ assert len(tree.children) == 1
292
+
173
293
 
174
294
  class TestCollectStats:
175
295
  """Tests for collect_stats function."""
@@ -229,6 +229,7 @@ class TestBuildTreeOptional:
229
229
  """Good path: optional dependencies are marked in the tree."""
230
230
  tree = build_tree(provider, "parent", include_optional=True)
231
231
 
232
+ assert isinstance(tree, Tree)
232
233
  # Find optional children
233
234
  optional_count = 0
234
235
  for child in tree.children:
@@ -246,6 +247,7 @@ class TestBuildTreeOptional:
246
247
  """Good path: required dependencies don't have optional marker."""
247
248
  tree = build_tree(provider, "parent", include_optional=True)
248
249
 
250
+ assert isinstance(tree, Tree)
249
251
  # The root should not have optional marker
250
252
  label = str(tree.label)
251
253
  assert "(optional)" not in label
@@ -255,6 +257,7 @@ class TestBuildTreeOptional:
255
257
  """Critical path: is_optional_dep parameter adds marker."""
256
258
  tree = build_tree(provider, "required-child", is_optional_dep=True)
257
259
 
260
+ assert isinstance(tree, Tree)
258
261
  label = str(tree.label)
259
262
  assert "(optional)" in label
260
263
 
@@ -179,3 +179,54 @@ class TestJsonReporterParseLabel:
179
179
 
180
180
  assert "[" not in result.raw
181
181
  assert "]" not in result.raw
182
+
183
+
184
+ class TestJsonReporterMissingOnly:
185
+ """Tests for JsonReporter with missing_only flag."""
186
+
187
+ @pytest.mark.unit
188
+ def test_missing_only_metadata_included(self, sample_report_data):
189
+ """Good path: missing_only is included in metadata."""
190
+ sample_report_data.missing_only = True
191
+ reporter = JsonReporter()
192
+
193
+ result = reporter.generate(sample_report_data)
194
+ parsed = json.loads(result)
195
+
196
+ assert "missing_only" in parsed["metadata"]
197
+ assert parsed["metadata"]["missing_only"] is True
198
+
199
+ @pytest.mark.unit
200
+ def test_missing_only_excludes_packaged_packages(self, sample_report_data):
201
+ """Good path: excludes packaged packages when missing_only is True."""
202
+ sample_report_data.missing_only = True
203
+ reporter = JsonReporter()
204
+
205
+ result = reporter.generate(sample_report_data)
206
+ parsed = json.loads(result)
207
+
208
+ assert parsed["packaged_packages"] == []
209
+
210
+ @pytest.mark.unit
211
+ def test_missing_only_false_includes_packaged_packages(self, sample_report_data):
212
+ """Good path: includes packaged packages when missing_only is False."""
213
+ sample_report_data.missing_only = False
214
+ reporter = JsonReporter()
215
+
216
+ result = reporter.generate(sample_report_data)
217
+ parsed = json.loads(result)
218
+
219
+ assert len(parsed["packaged_packages"]) > 0
220
+ assert "packaged-a" in parsed["packaged_packages"]
221
+
222
+ @pytest.mark.unit
223
+ def test_missing_only_still_includes_missing_packages(self, sample_report_data):
224
+ """Good path: missing packages are still included when missing_only is True."""
225
+ sample_report_data.missing_only = True
226
+ reporter = JsonReporter()
227
+
228
+ result = reporter.generate(sample_report_data)
229
+ parsed = json.loads(result)
230
+
231
+ assert len(parsed["missing_packages"]) > 0
232
+ assert "missing-a" in parsed["missing_packages"]
@@ -133,3 +133,68 @@ class TestMarkdownReporterTreeToText:
133
133
 
134
134
  # Should contain tree structure characters
135
135
  assert "├──" in result or "└──" in result
136
+
137
+
138
+ class TestMarkdownReporterMissingOnly:
139
+ """Tests for MarkdownReporter with missing_only flag."""
140
+
141
+ @pytest.mark.unit
142
+ def test_missing_only_metadata_included(self, sample_report_data):
143
+ """Good path: shows missing_only indicator in metadata."""
144
+ sample_report_data.missing_only = True
145
+ reporter = MarkdownReporter()
146
+
147
+ result = reporter.generate(sample_report_data)
148
+
149
+ assert "**Missing only:** Yes" in result
150
+
151
+ @pytest.mark.unit
152
+ def test_missing_only_excludes_packaged_section(self, sample_report_data):
153
+ """Good path: excludes packaged packages section when missing_only is True."""
154
+ sample_report_data.missing_only = True
155
+ reporter = MarkdownReporter()
156
+
157
+ result = reporter.generate(sample_report_data)
158
+
159
+ assert "## Packaged Packages" not in result
160
+
161
+ @pytest.mark.unit
162
+ def test_missing_only_excludes_dependency_tree(self, sample_report_data):
163
+ """Good path: excludes dependency tree when missing_only is True."""
164
+ sample_report_data.missing_only = True
165
+ reporter = MarkdownReporter()
166
+
167
+ result = reporter.generate(sample_report_data)
168
+
169
+ assert "## Dependency Tree" not in result
170
+
171
+ @pytest.mark.unit
172
+ def test_missing_only_false_includes_packaged_section(self, sample_report_data):
173
+ """Good path: includes packaged section when missing_only is False."""
174
+ sample_report_data.missing_only = False
175
+ reporter = MarkdownReporter()
176
+
177
+ result = reporter.generate(sample_report_data)
178
+
179
+ assert "## Packaged Packages" in result
180
+
181
+ @pytest.mark.unit
182
+ def test_missing_only_false_includes_dependency_tree(self, sample_report_data):
183
+ """Good path: includes dependency tree when missing_only is False."""
184
+ sample_report_data.missing_only = False
185
+ reporter = MarkdownReporter()
186
+
187
+ result = reporter.generate(sample_report_data)
188
+
189
+ assert "## Dependency Tree" in result
190
+
191
+ @pytest.mark.unit
192
+ def test_missing_only_still_includes_missing_packages(self, sample_report_data):
193
+ """Good path: missing packages are still shown when missing_only is True."""
194
+ sample_report_data.missing_only = True
195
+ reporter = MarkdownReporter()
196
+
197
+ result = reporter.generate(sample_report_data)
198
+
199
+ assert "## Missing Packages" in result
200
+ assert "- `missing-a`" in result
@@ -102,3 +102,32 @@ class TestStdoutReporterGenerate:
102
102
  for arg in args:
103
103
  if isinstance(arg, str):
104
104
  assert "Missing packages that need packaging" not in arg
105
+
106
+ @pytest.mark.unit
107
+ def test_missing_only_skips_dependency_tree(self, sample_report_data, mock_console):
108
+ """Good path: skips dependency tree when missing_only is True."""
109
+ sample_report_data.missing_only = True
110
+ reporter = StdoutReporter(console=mock_console)
111
+
112
+ reporter.generate(sample_report_data)
113
+
114
+ # Should not print "Dependency Tree:"
115
+ for call_obj in mock_console.print.call_args_list:
116
+ args = call_obj[0] if call_obj[0] else []
117
+ for arg in args:
118
+ if isinstance(arg, str):
119
+ assert "Dependency Tree:" not in arg
120
+
121
+ @pytest.mark.unit
122
+ def test_missing_only_false_shows_dependency_tree(
123
+ self, sample_report_data, mock_console
124
+ ):
125
+ """Good path: shows dependency tree when missing_only is False."""
126
+ sample_report_data.missing_only = False
127
+ reporter = StdoutReporter(console=mock_console)
128
+
129
+ reporter.generate(sample_report_data)
130
+
131
+ # Should print "Dependency Tree:"
132
+ calls_str = str(mock_console.print.call_args_list)
133
+ assert "Dependency Tree" in calls_str