Coverage for tests / unit / utils / summary / test_display.py: 100%
129 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unit tests for print_summary_table in summary_tables module.
3Tests cover:
4- print_summary_table for CHECK, FIX, and TEST actions
5- Multiple tools display
6- Edge cases including empty results and unknown tools
7- Module constants and their usage
8"""
10from __future__ import annotations
12from typing import TYPE_CHECKING
14from assertpy import assert_that
16from lintro.enums.action import Action
17from lintro.utils.summary_tables import (
18 DEFAULT_REMAINING_COUNT,
19 print_summary_table,
20)
22if TYPE_CHECKING:
23 from collections.abc import Callable
25 from tests.unit.utils.conftest import FakeToolResult
28# =============================================================================
29# Tests for print_summary_table with CHECK action
30# =============================================================================
33def test_check_success_no_issues(
34 console_capture: tuple[Callable[[str], None], list[str]],
35 fake_tool_result_factory: Callable[..., FakeToolResult],
36) -> None:
37 """Display passing check with no issues shows PASS status and tool name.
39 Args:
40 console_capture: Mock console output capture.
41 fake_tool_result_factory: Factory for creating fake tool results.
42 """
43 capture, output = console_capture
44 result = fake_tool_result_factory(name="ruff", success=True, issues_count=0)
46 print_summary_table(capture, Action.CHECK, [result])
48 combined = "".join(output)
49 assert_that(combined).contains("ruff")
50 assert_that(combined).contains("PASS")
53def test_check_with_issues(
54 console_capture: tuple[Callable[[str], None], list[str]],
55 fake_tool_result_factory: Callable[..., FakeToolResult],
56) -> None:
57 """Display failing check with issues shows FAIL status and issue count.
59 Args:
60 console_capture: Mock console output capture.
61 fake_tool_result_factory: Factory for creating fake tool results.
62 """
63 capture, output = console_capture
64 result = fake_tool_result_factory(name="ruff", success=True, issues_count=5)
66 print_summary_table(capture, Action.CHECK, [result])
68 combined = "".join(output)
69 assert_that(combined).contains("ruff")
70 assert_that(combined).contains("FAIL")
71 assert_that(combined).contains("5")
74def test_check_execution_failure(
75 console_capture: tuple[Callable[[str], None], list[str]],
76 fake_tool_result_factory: Callable[..., FakeToolResult],
77) -> None:
78 """Display check with execution failure shows FAIL status.
80 Args:
81 console_capture: Mock console output capture.
82 fake_tool_result_factory: Factory for creating fake tool results.
83 """
84 capture, output = console_capture
85 result = fake_tool_result_factory(
86 name="ruff",
87 success=False,
88 issues_count=0,
89 output="timeout occurred",
90 )
92 print_summary_table(capture, Action.CHECK, [result])
94 combined = "".join(output)
95 assert_that(combined).contains("FAIL")
98def test_check_skipped_tool(
99 console_capture: tuple[Callable[[str], None], list[str]],
100 fake_tool_result_factory: Callable[..., FakeToolResult],
101) -> None:
102 """Display skipped status for version check failures.
104 Args:
105 console_capture: Mock console output capture.
106 fake_tool_result_factory: Factory for creating fake tool results.
107 """
108 capture, output = console_capture
109 result = fake_tool_result_factory(
110 name="ruff",
111 success=False,
112 issues_count=0,
113 output="Skipping ruff: version check failed",
114 )
116 print_summary_table(capture, Action.CHECK, [result])
118 combined = "".join(output)
119 assert_that(combined).contains("SKIPPED")
122# =============================================================================
123# Tests for print_summary_table with FIX action
124# =============================================================================
127def test_fix_with_fixed_count(
128 console_capture: tuple[Callable[[str], None], list[str]],
129 fake_tool_result_factory: Callable[..., FakeToolResult],
130) -> None:
131 """Display fixed count and remaining for fix action with columns present.
133 Args:
134 console_capture: Mock console output capture.
135 fake_tool_result_factory: Factory for creating fake tool results.
136 """
137 capture, output = console_capture
138 result = fake_tool_result_factory(
139 name="black",
140 success=True,
141 issues_count=0,
142 fixed_issues_count=3,
143 remaining_issues_count=0,
144 )
146 print_summary_table(capture, Action.FIX, [result])
148 combined = "".join(output)
149 assert_that(combined).contains("black")
150 assert_that(combined).contains("PASS")
151 assert_that(combined).contains("Fixed")
152 assert_that(combined).contains("AI-Applied")
153 assert_that(combined).contains("AI-Resolved")
154 assert_that(combined).contains("Remaining")
157def test_fix_with_remaining_issues(
158 console_capture: tuple[Callable[[str], None], list[str]],
159 fake_tool_result_factory: Callable[..., FakeToolResult],
160) -> None:
161 """Display remaining issues for fix action when some issues cannot be fixed.
163 Args:
164 console_capture: Mock console output capture.
165 fake_tool_result_factory: Factory for creating fake tool results.
166 """
167 capture, output = console_capture
168 result = fake_tool_result_factory(
169 name="black",
170 success=True,
171 issues_count=0,
172 fixed_issues_count=5,
173 remaining_issues_count=2,
174 )
176 print_summary_table(capture, Action.FIX, [result])
178 combined = "".join(output)
179 assert_that(combined).contains("black")
182def test_fix_no_files_shows_zero(
183 console_capture: tuple[Callable[[str], None], list[str]],
184 fake_tool_result_factory: Callable[..., FakeToolResult],
185) -> None:
186 """Display PASS with 0 fixed/remaining when no files to format.
188 Note: "No files to format" means the tool ran successfully but found no
189 files - this is PASS with 0 issues, not SKIPPED (consistent with check mode).
191 Args:
192 console_capture: Mock console output capture.
193 fake_tool_result_factory: Factory for creating fake tool results.
194 """
195 capture, output = console_capture
196 result = fake_tool_result_factory(
197 name="black",
198 success=True,
199 issues_count=0,
200 output="No files to format",
201 )
203 print_summary_table(capture, Action.FIX, [result])
205 combined = "".join(output)
206 assert_that(combined).contains("PASS")
207 # Should show 0 for both Fixed and Remaining, not SKIPPED
208 assert_that(combined).does_not_contain("SKIPPED")
211def test_fix_parsing_remaining_from_output(
212 console_capture: tuple[Callable[[str], None], list[str]],
213 fake_tool_result_factory: Callable[..., FakeToolResult],
214) -> None:
215 """Parse remaining issues from output when not explicitly provided.
217 Args:
218 console_capture: Mock console output capture.
219 fake_tool_result_factory: Factory for creating fake tool results.
220 """
221 capture, output = console_capture
222 result = fake_tool_result_factory(
223 name="black",
224 success=True,
225 issues_count=3,
226 output="Found 2 issue(s) that cannot be auto-fixed",
227 )
229 print_summary_table(capture, Action.FIX, [result])
231 combined = "".join(output)
232 assert_that(combined).contains("black")
235def test_fix_shows_ai_fixed_count_from_metadata(
236 console_capture: tuple[Callable[[str], None], list[str]],
237 fake_tool_result_factory: Callable[..., FakeToolResult],
238) -> None:
239 """Display AI columns when AI metadata provides per-tool counts.
241 Args:
242 console_capture: Mock console output capture.
243 fake_tool_result_factory: Factory for creating fake tool results.
244 """
245 capture, output = console_capture
246 result = fake_tool_result_factory(
247 name="ruff",
248 success=False,
249 fixed_issues_count=6,
250 remaining_issues_count=5,
251 )
252 result.ai_metadata = {
253 "applied_count": 4,
254 "verified_count": 3,
255 "unverified_count": 1,
256 }
258 print_summary_table(capture, Action.FIX, [result])
260 combined = "".join(output)
261 assert_that(combined).contains("AI-Applied")
262 assert_that(combined).contains("AI-Resolved")
263 assert_that(combined).contains("4")
264 assert_that(combined).contains("3")
265 assert_that(combined).contains("1 unresolved")
268# =============================================================================
269# Tests for print_summary_table with TEST action
270# =============================================================================
273def test_pytest_with_summary(
274 console_capture: tuple[Callable[[str], None], list[str]],
275 fake_tool_result_factory: Callable[..., FakeToolResult],
276) -> None:
277 """Display pytest summary with detailed metrics including all columns.
279 Args:
280 console_capture: Mock console output capture.
281 fake_tool_result_factory: Factory for creating fake tool results.
282 """
283 capture, output = console_capture
284 result = fake_tool_result_factory(
285 name="pytest",
286 success=True,
287 issues_count=0,
288 pytest_summary={
289 "passed": 10,
290 "failed": 2,
291 "skipped": 1,
292 "duration": 1.5,
293 "total": 13,
294 },
295 )
297 print_summary_table(capture, Action.TEST, [result])
299 combined = "".join(output)
300 assert_that(combined).contains("pytest")
301 assert_that(combined).contains("Passed")
302 assert_that(combined).contains("Failed")
303 assert_that(combined).contains("Skipped")
304 assert_that(combined).contains("Duration")
307def test_non_pytest_test_tool(
308 console_capture: tuple[Callable[[str], None], list[str]],
309 fake_tool_result_factory: Callable[..., FakeToolResult],
310) -> None:
311 """Display basic pass/fail for non-pytest tools in test action.
313 Tool names with underscores are displayed with hyphens for consistency.
315 Args:
316 console_capture: Mock console output capture.
317 fake_tool_result_factory: Factory for creating fake tool results.
318 """
319 capture, output = console_capture
320 result = fake_tool_result_factory(
321 name="other_test_runner",
322 success=True,
323 issues_count=0,
324 )
326 print_summary_table(capture, Action.TEST, [result])
328 combined = "".join(output)
329 # Underscores are converted to hyphens for display
330 assert_that(combined).contains("other-test-runner")
331 assert_that(combined).does_not_contain(
332 "other_test_runner",
333 ) # original with underscore
334 assert_that(combined).contains("PASS")
337# =============================================================================
338# Tests for print_summary_table with multiple tools
339# =============================================================================
342def test_multiple_tools_displayed(
343 console_capture: tuple[Callable[[str], None], list[str]],
344 fake_tool_result_factory: Callable[..., FakeToolResult],
345) -> None:
346 """Display all tools in the summary table when multiple tools are run.
348 Args:
349 console_capture: Mock console output capture.
350 fake_tool_result_factory: Factory for creating fake tool results.
351 """
352 capture, output = console_capture
353 results = [
354 fake_tool_result_factory(name="ruff", success=True, issues_count=0),
355 fake_tool_result_factory(name="black", success=True, issues_count=2),
356 fake_tool_result_factory(name="mypy", success=False, issues_count=5),
357 ]
359 print_summary_table(capture, Action.CHECK, results)
361 combined = "".join(output)
362 assert_that(combined).contains("ruff")
363 assert_that(combined).contains("black")
364 assert_that(combined).contains("mypy")
367def test_tools_sorted_alphabetically(
368 console_capture: tuple[Callable[[str], None], list[str]],
369 fake_tool_result_factory: Callable[..., FakeToolResult],
370) -> None:
371 """Display tools in alphabetical order regardless of input order.
373 Args:
374 console_capture: Mock console output capture.
375 fake_tool_result_factory: Factory for creating fake tool results.
376 """
377 capture, output = console_capture
378 # Input in non-alphabetical order: ruff, bandit, clippy
379 results = [
380 fake_tool_result_factory(name="ruff", success=True, issues_count=0),
381 fake_tool_result_factory(name="bandit", success=True, issues_count=0),
382 fake_tool_result_factory(name="clippy", success=True, issues_count=0),
383 ]
385 print_summary_table(capture, Action.CHECK, results)
387 combined = "".join(output)
388 # Verify alphabetical order: bandit < clippy < ruff
389 bandit_pos = combined.find("bandit")
390 clippy_pos = combined.find("clippy")
391 ruff_pos = combined.find("ruff")
393 assert_that(bandit_pos).is_less_than(clippy_pos)
394 assert_that(clippy_pos).is_less_than(ruff_pos)
397# =============================================================================
398# Tests for edge cases in print_summary_table
399# =============================================================================
402def test_empty_results_list(
403 console_capture: tuple[Callable[[str], None], list[str]],
404) -> None:
405 """Handle empty results list gracefully by still producing output.
407 Args:
408 console_capture: Mock console output capture.
409 """
410 capture, output = console_capture
412 print_summary_table(capture, Action.CHECK, [])
414 # Should output something (table headers even if empty)
415 assert_that(output).is_not_empty()
418def test_unknown_tool_name(
419 console_capture: tuple[Callable[[str], None], list[str]],
420 fake_tool_result_factory: Callable[..., FakeToolResult],
421) -> None:
422 """Handle unknown tool name gracefully with underscore-to-hyphen conversion.
424 Tool names with underscores are displayed with hyphens for consistency
425 with actual CLI tool naming conventions.
427 Args:
428 console_capture: Mock console output capture.
429 fake_tool_result_factory: Factory for creating fake tool results.
430 """
431 capture, output = console_capture
432 result = fake_tool_result_factory(
433 name="unknown_tool_xyz",
434 success=True,
435 issues_count=0,
436 )
438 print_summary_table(capture, Action.CHECK, [result])
440 combined = "".join(output)
441 # Underscores are converted to hyphens for display
442 assert_that(combined).contains("unknown-tool-xyz")
443 assert_that(combined).does_not_contain(
444 "unknown_tool_xyz",
445 ) # original with underscore
448# =============================================================================
449# Tests for module constants
450# =============================================================================
453def test_default_remaining_count_is_question_mark() -> None:
454 """Verify DEFAULT_REMAINING_COUNT is the expected sentinel value."""
455 assert_that(DEFAULT_REMAINING_COUNT).is_equal_to("?")
456 assert_that(DEFAULT_REMAINING_COUNT).is_instance_of(str)
459def test_default_remaining_count_used_in_fix_output(
460 console_capture: tuple[Callable[[str], None], list[str]],
461 fake_tool_result_factory: Callable[..., FakeToolResult],
462) -> None:
463 """Verify DEFAULT_REMAINING_COUNT is used when remaining count is unknown.
465 When a tool fails but the remaining issue count cannot be determined,
466 the constant should appear in the output as a fallback indicator.
468 Args:
469 console_capture: Mock console output capture.
470 fake_tool_result_factory: Factory for creating fake tool results.
471 """
472 capture, output = console_capture
473 result = fake_tool_result_factory(
474 name="ruff",
475 success=False,
476 issues_count=0,
477 output="some remaining issues exist",
478 remaining_issues_count=None,
479 fixed_issues_count=None,
480 )
482 print_summary_table(capture, Action.FIX, [result])
484 combined = "".join(output)
485 # The "?" should appear in the remaining column when count is unknown
486 assert_that(combined).contains(DEFAULT_REMAINING_COUNT)