Coverage for tests / unit / utils / output / test_file_writer_format.py: 100%
48 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 format_tool_output function.
3This module tests the output formatting functions for transforming
4tool outputs into various display formats.
5"""
7from __future__ import annotations
9import json
11import pytest
12from assertpy import assert_that
14from lintro.utils.output.file_writer import (
15 TABULATE_AVAILABLE,
16 format_tool_output,
17)
19# =============================================================================
20# Tests for format_tool_output - Empty/whitespace handling
21# =============================================================================
24@pytest.mark.parametrize(
25 ("raw_output", "expected"),
26 [
27 ("", "No issues found."),
28 (" ", "No issues found."),
29 ("\n\t ", "No issues found."),
30 (" \n \t \n ", "No issues found."),
31 ],
32 ids=["empty", "spaces", "whitespace-mix", "multiline-whitespace"],
33)
34def test_format_tool_output_returns_no_issues_for_empty_input(
35 raw_output: str,
36 expected: str,
37) -> None:
38 """Verify empty or whitespace-only output returns 'No issues found' message.
40 Args:
41 raw_output: Description of raw_output (str).
42 expected: Description of expected (str).
43 """
44 result = format_tool_output("ruff", raw_output)
45 assert_that(result).is_equal_to(expected)
48# =============================================================================
49# Tests for format_tool_output - Issue formatting
50# =============================================================================
53def test_format_tool_output_formats_provided_issues() -> None:
54 """Verify provided issues are formatted using the issue formatter.
56 When real issue objects with to_display_row are provided, the formatter
57 should process them and return formatted output with file and line info.
58 """
59 from lintro.parsers.ruff.ruff_issue import RuffIssue
61 issues = [
62 RuffIssue(
63 file="src/main.py",
64 line=10,
65 column=5,
66 code="E001",
67 message="Error message",
68 fixable=False,
69 ),
70 ]
72 result = format_tool_output("ruff", "", issues=issues)
74 assert_that(result).is_instance_of(str)
75 assert_that(result).is_not_empty()
76 # The formatted output should contain the issue data
77 assert_that(result).contains("src/main.py")
78 assert_that(result).contains("E001")
81def test_format_tool_output_falls_back_to_raw_for_unknown_tool() -> None:
82 """Verify unrecognized tool output is returned as-is when parsing fails."""
83 raw_output = "Unknown format that can't be parsed"
85 result = format_tool_output("unknown-tool", raw_output)
87 assert_that(result).is_equal_to(raw_output)
90# =============================================================================
91# Tests for format_tool_output - Tool-specific parsing
92# =============================================================================
95@pytest.mark.parametrize(
96 ("tool_name", "raw_output", "expected_content"),
97 [
98 (
99 "ruff",
100 "src/main.py:10:5: E001 Error message",
101 "src/main.py",
102 ),
103 (
104 "mypy",
105 "src/main.py:10: error: Type error [type-error]",
106 "src/main.py",
107 ),
108 (
109 "black",
110 "would reformat src/main.py",
111 "src/main.py",
112 ),
113 (
114 "hadolint",
115 "Dockerfile:10 DL3006 warning: Always tag the version",
116 "Dockerfile",
117 ),
118 (
119 "actionlint",
120 ".github/workflows/ci.yml:10:1: error: Syntax error [syntax]",
121 ".github/workflows/ci.yml",
122 ),
123 (
124 "pydoclint",
125 "src/main.py:10:1: DOC101 Missing docstring",
126 "src/main.py",
127 ),
128 (
129 "markdownlint",
130 "README.md:10: MD013 Line too long",
131 "README.md",
132 ),
133 (
134 "clippy",
135 "warning: unused variable\n --> src/main.rs:10:5",
136 "src/main.rs",
137 ),
138 (
139 "pytest",
140 "FAILED tests/test_main.py::test_func - AssertionError",
141 "tests/test_main.py",
142 ),
143 (
144 "yamllint",
145 "config.yml\n 10:1 warning trailing spaces (trailing-spaces)",
146 "config.yml",
147 ),
148 ],
149 ids=[
150 "ruff",
151 "mypy",
152 "black",
153 "hadolint",
154 "actionlint",
155 "pydoclint",
156 "markdownlint",
157 "clippy",
158 "pytest",
159 "yamllint",
160 ],
161)
162def test_format_tool_output_parses_tool_specific_formats(
163 tool_name: str,
164 raw_output: str,
165 expected_content: str,
166) -> None:
167 """Verify tool-specific output is parsed and formatted with file references.
169 Each tool has its own output format. The parser should extract meaningful
170 information like file paths and include them in the formatted result.
172 Args:
173 tool_name: Name of the linting tool.
174 raw_output: Raw output from the tool.
175 expected_content: Expected content to be found in the formatted output.
176 """
177 result = format_tool_output(tool_name, raw_output, output_format="plain")
179 assert_that(result).is_instance_of(str)
180 assert_that(result).is_not_empty()
181 # The result should contain the file reference from the parsed output
182 assert_that(result).contains(expected_content)
185def test_format_tool_output_parses_bandit_json() -> None:
186 """Verify bandit JSON output is parsed with security issue details."""
187 bandit_output = json.dumps(
188 {
189 "results": [
190 {
191 "filename": "src/main.py",
192 "line_number": 10,
193 "test_id": "B101",
194 "issue_text": "Security issue",
195 "issue_severity": "HIGH",
196 "issue_confidence": "HIGH",
197 },
198 ],
199 },
200 )
202 result = format_tool_output("bandit", bandit_output, output_format="plain")
204 assert_that(result).is_instance_of(str)
205 assert_that(result).is_not_empty()
206 assert_that(result).contains("src/main.py")
207 assert_that(result).contains("B101")
210def test_format_tool_output_handles_invalid_bandit_json() -> None:
211 """Verify invalid bandit JSON returns error with raw output for debugging."""
212 bandit_output = "not valid json { broken"
214 result = format_tool_output("bandit", bandit_output, output_format="plain")
216 # Should return error message with raw output for debugging
217 assert_that(result).contains("Error:")
218 assert_that(result).contains("Failed to parse Bandit output")
219 assert_that(result).contains("Raw output:")
220 assert_that(result).contains(bandit_output)
223@pytest.mark.parametrize(
224 ("output_format_str",),
225 [
226 ("grid",),
227 ("plain",),
228 ("GRID",),
229 ("Plain",),
230 ],
231 ids=["grid-lower", "plain-lower", "grid-upper", "plain-mixed"],
232)
233def test_format_tool_output_normalizes_format_string(output_format_str: str) -> None:
234 """Verify output format strings are normalized case-insensitively.
236 Args:
237 output_format_str: Output format string to normalize.
238 """
239 result = format_tool_output("ruff", "", output_format=output_format_str)
241 assert_that(result).is_equal_to("No issues found.")
244# =============================================================================
245# Tests for tabulate availability
246# =============================================================================
249def test_tabulate_available_flag_is_true_in_test_environment() -> None:
250 """Verify tabulate library is available and flag is set correctly.
252 The test environment should have tabulate installed, so this flag
253 should be True, enabling table formatting features.
254 """
255 assert_that(TABULATE_AVAILABLE).is_instance_of(bool)
256 assert_that(TABULATE_AVAILABLE).is_true()