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

1"""Unit tests for format_tool_output function. 

2 

3This module tests the output formatting functions for transforming 

4tool outputs into various display formats. 

5""" 

6 

7from __future__ import annotations 

8 

9import json 

10 

11import pytest 

12from assertpy import assert_that 

13 

14from lintro.utils.output.file_writer import ( 

15 TABULATE_AVAILABLE, 

16 format_tool_output, 

17) 

18 

19# ============================================================================= 

20# Tests for format_tool_output - Empty/whitespace handling 

21# ============================================================================= 

22 

23 

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. 

39 

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) 

46 

47 

48# ============================================================================= 

49# Tests for format_tool_output - Issue formatting 

50# ============================================================================= 

51 

52 

53def test_format_tool_output_formats_provided_issues() -> None: 

54 """Verify provided issues are formatted using the issue formatter. 

55 

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 

60 

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 ] 

71 

72 result = format_tool_output("ruff", "", issues=issues) 

73 

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") 

79 

80 

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" 

84 

85 result = format_tool_output("unknown-tool", raw_output) 

86 

87 assert_that(result).is_equal_to(raw_output) 

88 

89 

90# ============================================================================= 

91# Tests for format_tool_output - Tool-specific parsing 

92# ============================================================================= 

93 

94 

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. 

168 

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. 

171 

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") 

178 

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) 

183 

184 

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 ) 

201 

202 result = format_tool_output("bandit", bandit_output, output_format="plain") 

203 

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") 

208 

209 

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" 

213 

214 result = format_tool_output("bandit", bandit_output, output_format="plain") 

215 

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) 

221 

222 

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. 

235 

236 Args: 

237 output_format_str: Output format string to normalize. 

238 """ 

239 result = format_tool_output("ruff", "", output_format=output_format_str) 

240 

241 assert_that(result).is_equal_to("No issues found.") 

242 

243 

244# ============================================================================= 

245# Tests for tabulate availability 

246# ============================================================================= 

247 

248 

249def test_tabulate_available_flag_is_true_in_test_environment() -> None: 

250 """Verify tabulate library is available and flag is set correctly. 

251 

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()