Coverage for tests / unit / formatters / test_format_issues.py: 100%
137 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"""Tests for format_issues and format_issues_with_sections functions.
3Tests the unified table formatting of issues from different tools,
4verifying correct column structure and content.
5"""
7from __future__ import annotations
9import pytest
10from assertpy import assert_that
12from lintro.enums.display_column import STANDARD_COLUMNS, DisplayColumn
13from lintro.formatters.formatter import (
14 format_issues,
15 format_issues_with_sections,
16)
17from lintro.parsers.bandit.bandit_issue import BanditIssue
18from lintro.parsers.base_issue import BaseIssue
19from lintro.parsers.black.black_issue import BlackIssue
20from lintro.parsers.ruff.ruff_format_issue import RuffFormatIssue
21from lintro.parsers.ruff.ruff_issue import RuffIssue
23# =============================================================================
24# Tests for STANDARD_COLUMNS constant
25# =============================================================================
28def test_standard_columns_has_expected_fields() -> None:
29 """Verify STANDARD_COLUMNS contains all expected fields including Severity and Fixable."""
30 assert_that(STANDARD_COLUMNS).is_equal_to(
31 [
32 DisplayColumn.FILE,
33 DisplayColumn.LINE,
34 DisplayColumn.COLUMN,
35 DisplayColumn.CODE,
36 DisplayColumn.SEVERITY,
37 DisplayColumn.FIXABLE,
38 DisplayColumn.MESSAGE,
39 ],
40 )
43# =============================================================================
44# Tests for format_issues with RuffIssue
45# =============================================================================
48def test_format_issues_with_ruff_issue_contains_standard_columns() -> None:
49 """Verify RuffIssue formatted output contains all standard column headers."""
50 issues = [
51 RuffIssue(
52 file="src/main.py",
53 line=10,
54 column=5,
55 code="F401",
56 message="unused import",
57 fixable=True,
58 ),
59 ]
61 result = format_issues(issues, output_format="grid")
63 assert_that(result).contains("File")
64 assert_that(result).contains("Line")
65 assert_that(result).contains("Column")
66 assert_that(result).contains("Code")
67 assert_that(result).contains("Severity")
68 assert_that(result).contains("Fixable")
69 assert_that(result).contains("Message")
72def test_format_issues_with_ruff_issue_contains_issue_data() -> None:
73 """Verify RuffIssue formatted output contains the actual issue data."""
74 issues = [
75 RuffIssue(
76 file="src/main.py",
77 line=10,
78 column=5,
79 code="F401",
80 message="unused import",
81 fixable=True,
82 ),
83 ]
85 result = format_issues(issues, output_format="grid")
87 assert_that(result).contains("src/main.py")
88 assert_that(result).contains("10")
89 assert_that(result).contains("5")
90 assert_that(result).contains("F401")
91 assert_that(result).contains("unused import")
94def test_format_issues_shows_fixable_status() -> None:
95 """Verify Fixable column shows Yes for fixable=True status."""
96 issues = [
97 RuffIssue(
98 file="src/main.py",
99 line=10,
100 column=5,
101 code="F401",
102 message="unused import",
103 fixable=True,
104 ),
105 ]
107 result = format_issues(issues, output_format="grid")
109 assert_that(result).contains("Fixable")
110 assert_that(result).contains("Yes")
113def test_format_issues_shows_non_fixable_status() -> None:
114 """Verify Fixable column is empty for fixable=False status."""
115 issues = [
116 RuffIssue(
117 file="src/main.py",
118 line=10,
119 column=5,
120 code="D100",
121 message="missing docstring",
122 fixable=False,
123 ),
124 ]
126 result = format_issues(issues, output_format="grid")
128 assert_that(result).contains("Fixable")
129 # Non-fixable issues show empty string, not "Yes"
130 assert_that(result).does_not_contain("Yes")
133def test_format_issues_shows_severity() -> None:
134 """Verify Severity column shows severity values."""
135 issues = [
136 BanditIssue(
137 file="src/main.py",
138 line=10,
139 col_offset=5,
140 test_id="B101",
141 issue_text="Use assert_that instead of assert",
142 issue_severity="HIGH",
143 issue_confidence="HIGH",
144 ),
145 ]
147 result = format_issues(issues, output_format="grid")
149 assert_that(result).contains("Severity")
150 assert_that(result).contains("ERROR")
153# =============================================================================
154# Tests for format_issues with BlackIssue
155# =============================================================================
158def test_format_issues_with_black_issue_contains_standard_columns() -> None:
159 """Verify BlackIssue formatted output contains all standard column headers.
161 BlackIssue doesn't have meaningful Line/Column/Code values, but the
162 table should still show all standard columns for consistency.
163 """
164 issues = [
165 BlackIssue(
166 file="src/main.py",
167 message="Would reformat file",
168 ),
169 ]
171 result = format_issues(issues, output_format="grid")
173 assert_that(result).contains("File")
174 assert_that(result).contains("Line")
175 assert_that(result).contains("Column")
176 assert_that(result).contains("Code")
177 assert_that(result).contains("Severity")
178 assert_that(result).contains("Fixable")
179 assert_that(result).contains("Message")
180 assert_that(result).contains("src/main.py")
181 assert_that(result).contains("Would reformat file")
184# =============================================================================
185# Tests for format_issues with RuffFormatIssue
186# =============================================================================
189def test_format_issues_with_ruff_format_issue_contains_standard_columns() -> None:
190 """Verify RuffFormatIssue formatted output contains all standard columns.
192 RuffFormatIssue has a fixed code of 'FORMAT' and message 'Would reformat file'.
193 """
194 issues = [
195 RuffFormatIssue(
196 file="src/main.py",
197 ),
198 ]
200 result = format_issues(issues, output_format="grid")
202 assert_that(result).contains("File")
203 assert_that(result).contains("Line")
204 assert_that(result).contains("Column")
205 assert_that(result).contains("Code")
206 assert_that(result).contains("Severity")
207 assert_that(result).contains("Fixable")
208 assert_that(result).contains("Message")
209 assert_that(result).contains("src/main.py")
210 assert_that(result).contains("FORMAT")
211 assert_that(result).contains("Would reformat file")
214# =============================================================================
215# Tests for format_issues with BanditIssue
216# =============================================================================
219def test_format_issues_with_bandit_issue_uses_display_field_map() -> None:
220 """Verify BanditIssue uses its custom DISPLAY_FIELD_MAP for columns.
222 BanditIssue maps test_id -> code, issue_text -> message, issue_severity -> severity.
223 """
224 issues = [
225 BanditIssue(
226 file="src/main.py",
227 line=10,
228 col_offset=5,
229 test_id="B101",
230 issue_text="assert used",
231 issue_severity="LOW",
232 issue_confidence="HIGH",
233 ),
234 ]
236 result = format_issues(issues, output_format="grid")
238 assert_that(result).contains("src/main.py")
239 assert_that(result).contains("B101")
240 assert_that(result).contains("assert used")
243def test_format_issues_with_bandit_issue_shows_severity() -> None:
244 """Verify BanditIssue severity is shown in output by default."""
245 issues = [
246 BanditIssue(
247 file="src/main.py",
248 line=10,
249 col_offset=5,
250 test_id="B101",
251 issue_text="assert used",
252 issue_severity="HIGH",
253 issue_confidence="HIGH",
254 ),
255 ]
257 result = format_issues(issues, output_format="grid")
259 assert_that(result).contains("Severity")
260 assert_that(result).contains("ERROR")
263# =============================================================================
264# Tests for format_issues_with_sections
265# =============================================================================
268def test_format_issues_with_sections_groups_by_fixable() -> None:
269 """Verify format_issues_with_sections groups issues by fixable status."""
270 issues = [
271 RuffIssue(
272 file="a.py",
273 line=1,
274 column=1,
275 code="F401",
276 message="unused import",
277 fixable=True,
278 ),
279 RuffIssue(
280 file="b.py",
281 line=1,
282 column=1,
283 code="D100",
284 message="missing docstring",
285 fixable=False,
286 ),
287 ]
289 result = format_issues_with_sections(issues, group_by_fixable=True)
291 assert_that(result).contains("Auto-fixable issues")
292 assert_that(result).contains("Not auto-fixable issues")
293 assert_that(result).contains("a.py")
294 assert_that(result).contains("b.py")
297def test_format_issues_with_sections_only_fixable() -> None:
298 """Verify format_issues_with_sections handles only fixable issues."""
299 issues = [
300 RuffIssue(
301 file="a.py",
302 line=1,
303 column=1,
304 code="F401",
305 message="unused import",
306 fixable=True,
307 ),
308 ]
310 result = format_issues_with_sections(issues, group_by_fixable=True)
312 assert_that(result).contains("Auto-fixable issues")
313 assert_that(result).does_not_contain("Not auto-fixable issues")
316def test_format_issues_with_sections_only_non_fixable() -> None:
317 """Verify format_issues_with_sections handles only non-fixable issues."""
318 issues = [
319 RuffIssue(
320 file="a.py",
321 line=1,
322 column=1,
323 code="D100",
324 message="missing docstring",
325 fixable=False,
326 ),
327 ]
329 result = format_issues_with_sections(issues, group_by_fixable=True)
331 assert_that(result).does_not_contain("Auto-fixable issues")
332 assert_that(result).contains("Not auto-fixable issues")
335def test_format_issues_with_sections_without_grouping() -> None:
336 """Verify format_issues_with_sections without grouping returns single table."""
337 issues = [
338 RuffIssue(
339 file="a.py",
340 line=1,
341 column=1,
342 code="F401",
343 message="unused import",
344 fixable=True,
345 ),
346 RuffIssue(
347 file="b.py",
348 line=1,
349 column=1,
350 code="D100",
351 message="missing docstring",
352 fixable=False,
353 ),
354 ]
356 result = format_issues_with_sections(issues, group_by_fixable=False)
358 # Should not have section headers when not grouping
359 assert_that(result).does_not_contain("Auto-fixable issues")
360 assert_that(result).does_not_contain("Not auto-fixable issues")
361 # But should still contain the data
362 assert_that(result).contains("a.py")
363 assert_that(result).contains("b.py")
366# =============================================================================
367# Tests for consistent column output across tools
368# =============================================================================
371@pytest.mark.parametrize(
372 "issue",
373 [
374 RuffIssue(
375 file="test.py",
376 line=1,
377 column=1,
378 code="F401",
379 message="test",
380 fixable=True,
381 ),
382 BlackIssue(file="test.py", message="test"),
383 RuffFormatIssue(file="test.py"),
384 BanditIssue(
385 file="test.py",
386 line=1,
387 col_offset=1,
388 test_id="B101",
389 issue_text="test",
390 issue_severity="LOW",
391 issue_confidence="HIGH",
392 ),
393 ],
394 ids=["ruff", "black", "ruff_format", "bandit"],
395)
396def test_all_tool_issues_produce_tables_with_standard_columns(issue: BaseIssue) -> None:
397 """Verify all tool issue types produce tables with standard columns.
399 This ensures consistent output format regardless of which tool
400 generated the issues.
402 Args:
403 issue: A BaseIssue subclass instance from any supported tool.
404 """
405 result = format_issues([issue], output_format="grid")
407 # All tools should produce tables with these column headers
408 assert_that(result).contains("File")
409 assert_that(result).contains("Line")
410 assert_that(result).contains("Column")
411 assert_that(result).contains("Code")
412 assert_that(result).contains("Severity")
413 assert_that(result).contains("Fixable")
414 assert_that(result).contains("Message")
417# =============================================================================
418# Tests for empty issues
419# =============================================================================
422def test_format_issues_with_empty_list_returns_no_issues_message() -> None:
423 """Verify format_issues with empty list returns 'No issues found' message."""
424 result = format_issues([], output_format="grid")
426 assert_that(result).is_equal_to("No issues found.")
429def test_format_issues_with_sections_empty_list_returns_no_issues_message() -> None:
430 """Verify format_issues_with_sections with empty list returns 'No issues found' message."""
431 result = format_issues_with_sections([], group_by_fixable=True)
433 assert_that(result).is_equal_to("No issues found.")
436# =============================================================================
437# Tests for doc_url conditional column
438# =============================================================================
441def test_format_issues_shows_docs_column_when_doc_url_present() -> None:
442 """Verify Docs column appears in grid output when at least one issue has doc_url."""
443 issues = [
444 RuffIssue(
445 file="src/main.py",
446 line=10,
447 column=5,
448 code="E501",
449 message="Line too long",
450 doc_url="https://docs.astral.sh/ruff/rules/line-too-long/",
451 ),
452 ]
454 result = format_issues(issues, output_format="grid")
456 assert_that(result).contains("Docs")
457 assert_that(result).contains("https://docs.astral.sh/ruff/rules/line-too-long/")
460def test_format_issues_hides_docs_column_when_no_doc_url() -> None:
461 """Verify Docs column is absent when no issues have doc_url."""
462 issues = [
463 RuffIssue(
464 file="src/main.py",
465 line=10,
466 column=5,
467 code="E501",
468 message="Line too long",
469 ),
470 ]
472 result = format_issues(issues, output_format="grid")
474 assert_that(result).does_not_contain("Docs")
477def test_format_issues_docs_column_respects_explicit_columns() -> None:
478 """Verify explicit columns parameter is not overridden by doc_url detection."""
479 issues = [
480 RuffIssue(
481 file="src/main.py",
482 line=10,
483 column=5,
484 code="E501",
485 message="Line too long",
486 doc_url="https://docs.astral.sh/ruff/rules/line-too-long/",
487 ),
488 ]
490 result = format_issues(
491 issues,
492 output_format="grid",
493 columns=[DisplayColumn.FILE, DisplayColumn.CODE, DisplayColumn.MESSAGE],
494 )
496 assert_that(result).does_not_contain("Docs")
497 assert_that(result).contains("File")
498 assert_that(result).contains("Code")
499 assert_that(result).contains("Message")