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

1"""Tests for format_issues and format_issues_with_sections functions. 

2 

3Tests the unified table formatting of issues from different tools, 

4verifying correct column structure and content. 

5""" 

6 

7from __future__ import annotations 

8 

9import pytest 

10from assertpy import assert_that 

11 

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 

22 

23# ============================================================================= 

24# Tests for STANDARD_COLUMNS constant 

25# ============================================================================= 

26 

27 

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 ) 

41 

42 

43# ============================================================================= 

44# Tests for format_issues with RuffIssue 

45# ============================================================================= 

46 

47 

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 ] 

60 

61 result = format_issues(issues, output_format="grid") 

62 

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

70 

71 

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 ] 

84 

85 result = format_issues(issues, output_format="grid") 

86 

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

92 

93 

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 ] 

106 

107 result = format_issues(issues, output_format="grid") 

108 

109 assert_that(result).contains("Fixable") 

110 assert_that(result).contains("Yes") 

111 

112 

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 ] 

125 

126 result = format_issues(issues, output_format="grid") 

127 

128 assert_that(result).contains("Fixable") 

129 # Non-fixable issues show empty string, not "Yes" 

130 assert_that(result).does_not_contain("Yes") 

131 

132 

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 ] 

146 

147 result = format_issues(issues, output_format="grid") 

148 

149 assert_that(result).contains("Severity") 

150 assert_that(result).contains("ERROR") 

151 

152 

153# ============================================================================= 

154# Tests for format_issues with BlackIssue 

155# ============================================================================= 

156 

157 

158def test_format_issues_with_black_issue_contains_standard_columns() -> None: 

159 """Verify BlackIssue formatted output contains all standard column headers. 

160 

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 ] 

170 

171 result = format_issues(issues, output_format="grid") 

172 

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

182 

183 

184# ============================================================================= 

185# Tests for format_issues with RuffFormatIssue 

186# ============================================================================= 

187 

188 

189def test_format_issues_with_ruff_format_issue_contains_standard_columns() -> None: 

190 """Verify RuffFormatIssue formatted output contains all standard columns. 

191 

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 ] 

199 

200 result = format_issues(issues, output_format="grid") 

201 

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

212 

213 

214# ============================================================================= 

215# Tests for format_issues with BanditIssue 

216# ============================================================================= 

217 

218 

219def test_format_issues_with_bandit_issue_uses_display_field_map() -> None: 

220 """Verify BanditIssue uses its custom DISPLAY_FIELD_MAP for columns. 

221 

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 ] 

235 

236 result = format_issues(issues, output_format="grid") 

237 

238 assert_that(result).contains("src/main.py") 

239 assert_that(result).contains("B101") 

240 assert_that(result).contains("assert used") 

241 

242 

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 ] 

256 

257 result = format_issues(issues, output_format="grid") 

258 

259 assert_that(result).contains("Severity") 

260 assert_that(result).contains("ERROR") 

261 

262 

263# ============================================================================= 

264# Tests for format_issues_with_sections 

265# ============================================================================= 

266 

267 

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 ] 

288 

289 result = format_issues_with_sections(issues, group_by_fixable=True) 

290 

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

295 

296 

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 ] 

309 

310 result = format_issues_with_sections(issues, group_by_fixable=True) 

311 

312 assert_that(result).contains("Auto-fixable issues") 

313 assert_that(result).does_not_contain("Not auto-fixable issues") 

314 

315 

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 ] 

328 

329 result = format_issues_with_sections(issues, group_by_fixable=True) 

330 

331 assert_that(result).does_not_contain("Auto-fixable issues") 

332 assert_that(result).contains("Not auto-fixable issues") 

333 

334 

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 ] 

355 

356 result = format_issues_with_sections(issues, group_by_fixable=False) 

357 

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

364 

365 

366# ============================================================================= 

367# Tests for consistent column output across tools 

368# ============================================================================= 

369 

370 

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. 

398 

399 This ensures consistent output format regardless of which tool 

400 generated the issues. 

401 

402 Args: 

403 issue: A BaseIssue subclass instance from any supported tool. 

404 """ 

405 result = format_issues([issue], output_format="grid") 

406 

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

415 

416 

417# ============================================================================= 

418# Tests for empty issues 

419# ============================================================================= 

420 

421 

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

425 

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

427 

428 

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) 

432 

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

434 

435 

436# ============================================================================= 

437# Tests for doc_url conditional column 

438# ============================================================================= 

439 

440 

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 ] 

453 

454 result = format_issues(issues, output_format="grid") 

455 

456 assert_that(result).contains("Docs") 

457 assert_that(result).contains("https://docs.astral.sh/ruff/rules/line-too-long/") 

458 

459 

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 ] 

471 

472 result = format_issues(issues, output_format="grid") 

473 

474 assert_that(result).does_not_contain("Docs") 

475 

476 

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 ] 

489 

490 result = format_issues( 

491 issues, 

492 output_format="grid", 

493 columns=[DisplayColumn.FILE, DisplayColumn.CODE, DisplayColumn.MESSAGE], 

494 ) 

495 

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