Coverage for tests / unit / parsers / test_sqlfluff_parser.py: 100%

113 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Unit tests for SQLFluff parser.""" 

2 

3from __future__ import annotations 

4 

5import pytest 

6from assertpy import assert_that 

7 

8from lintro.parsers.sqlfluff.sqlfluff_parser import parse_sqlfluff_output 

9 

10 

11@pytest.mark.parametrize( 

12 "output", 

13 [ 

14 "", 

15 None, 

16 " \n \n ", 

17 "[]", 

18 "{}", 

19 ], 

20 ids=["empty", "none", "whitespace_only", "empty_array", "empty_object"], 

21) 

22def test_parse_sqlfluff_output_returns_empty_for_no_content( 

23 output: str | None, 

24) -> None: 

25 """Parse empty or whitespace-only output returns empty list. 

26 

27 Args: 

28 output: The SQLFluff output to parse. 

29 """ 

30 result = parse_sqlfluff_output(output) 

31 assert_that(result).is_empty() 

32 

33 

34def test_parse_sqlfluff_output_single_violation() -> None: 

35 """Parse single violation from SQLFluff JSON output.""" 

36 output = """[ 

37 { 

38 "filepath": "query.sql", 

39 "violations": [ 

40 { 

41 "start_line_no": 1, 

42 "start_line_pos": 1, 

43 "end_line_no": 1, 

44 "end_line_pos": 6, 

45 "code": "L010", 

46 "description": "Keywords must be upper case.", 

47 "name": "capitalisation.keywords" 

48 } 

49 ] 

50 } 

51]""" 

52 result = parse_sqlfluff_output(output) 

53 assert_that(result).is_length(1) 

54 assert_that(result[0].file).is_equal_to("query.sql") 

55 assert_that(result[0].line).is_equal_to(1) 

56 assert_that(result[0].column).is_equal_to(1) 

57 assert_that(result[0].end_line).is_equal_to(1) 

58 assert_that(result[0].end_column).is_equal_to(6) 

59 assert_that(result[0].code).is_equal_to("L010") 

60 assert_that(result[0].rule_name).is_equal_to("capitalisation.keywords") 

61 assert_that(result[0].message).is_equal_to("Keywords must be upper case.") 

62 

63 

64def test_parse_sqlfluff_output_multiple_violations_single_file() -> None: 

65 """Parse multiple violations from a single file.""" 

66 output = """[ 

67 { 

68 "filepath": "query.sql", 

69 "violations": [ 

70 { 

71 "start_line_no": 1, 

72 "start_line_pos": 1, 

73 "code": "L010", 

74 "description": "Keywords must be upper case.", 

75 "name": "capitalisation.keywords" 

76 }, 

77 { 

78 "start_line_no": 3, 

79 "start_line_pos": 5, 

80 "code": "L011", 

81 "description": "Implicit aliasing not allowed.", 

82 "name": "aliasing.table" 

83 } 

84 ] 

85 } 

86]""" 

87 result = parse_sqlfluff_output(output) 

88 assert_that(result).is_length(2) 

89 assert_that(result[0].line).is_equal_to(1) 

90 assert_that(result[0].code).is_equal_to("L010") 

91 assert_that(result[1].line).is_equal_to(3) 

92 assert_that(result[1].code).is_equal_to("L011") 

93 

94 

95def test_parse_sqlfluff_output_multiple_files() -> None: 

96 """Parse violations from multiple files.""" 

97 output = """[ 

98 { 

99 "filepath": "query1.sql", 

100 "violations": [ 

101 { 

102 "start_line_no": 1, 

103 "start_line_pos": 1, 

104 "code": "L010", 

105 "description": "Error 1", 

106 "name": "rule.one" 

107 } 

108 ] 

109 }, 

110 { 

111 "filepath": "query2.sql", 

112 "violations": [ 

113 { 

114 "start_line_no": 5, 

115 "start_line_pos": 10, 

116 "code": "L020", 

117 "description": "Error 2", 

118 "name": "rule.two" 

119 } 

120 ] 

121 } 

122]""" 

123 result = parse_sqlfluff_output(output) 

124 assert_that(result).is_length(2) 

125 assert_that(result[0].file).is_equal_to("query1.sql") 

126 assert_that(result[1].file).is_equal_to("query2.sql") 

127 

128 

129def test_parse_sqlfluff_output_file_with_no_violations() -> None: 

130 """Parse output where a file has no violations.""" 

131 output = """[ 

132 { 

133 "filepath": "clean.sql", 

134 "violations": [] 

135 } 

136]""" 

137 result = parse_sqlfluff_output(output) 

138 assert_that(result).is_empty() 

139 

140 

141def test_parse_sqlfluff_output_mixed_files() -> None: 

142 """Parse output with some files having violations and some not.""" 

143 output = """[ 

144 { 

145 "filepath": "clean.sql", 

146 "violations": [] 

147 }, 

148 { 

149 "filepath": "dirty.sql", 

150 "violations": [ 

151 { 

152 "start_line_no": 1, 

153 "start_line_pos": 1, 

154 "code": "L010", 

155 "description": "Error", 

156 "name": "rule.name" 

157 } 

158 ] 

159 } 

160]""" 

161 result = parse_sqlfluff_output(output) 

162 assert_that(result).is_length(1) 

163 assert_that(result[0].file).is_equal_to("dirty.sql") 

164 

165 

166def test_parse_sqlfluff_output_missing_optional_fields() -> None: 

167 """Parse violations with missing optional fields.""" 

168 output = """[ 

169 { 

170 "filepath": "query.sql", 

171 "violations": [ 

172 { 

173 "start_line_no": 1, 

174 "start_line_pos": 1, 

175 "code": "L010", 

176 "description": "Error message" 

177 } 

178 ] 

179 } 

180]""" 

181 result = parse_sqlfluff_output(output) 

182 assert_that(result).is_length(1) 

183 assert_that(result[0].end_line).is_none() 

184 assert_that(result[0].end_column).is_none() 

185 assert_that(result[0].rule_name).is_equal_to("") 

186 

187 

188def test_parse_sqlfluff_output_invalid_json() -> None: 

189 """Handle invalid JSON gracefully.""" 

190 output = "not valid json" 

191 result = parse_sqlfluff_output(output) 

192 assert_that(result).is_empty() 

193 

194 

195def test_parse_sqlfluff_output_not_a_list() -> None: 

196 """Handle non-list JSON gracefully.""" 

197 output = '{"filepath": "query.sql"}' 

198 result = parse_sqlfluff_output(output) 

199 assert_that(result).is_empty() 

200 

201 

202# ============================================================================= 

203# Edge case tests 

204# ============================================================================= 

205 

206 

207def test_parse_sqlfluff_output_unicode_in_message() -> None: 

208 """Handle Unicode characters in error messages.""" 

209 output = """[ 

210 { 

211 "filepath": "query.sql", 

212 "violations": [ 

213 { 

214 "start_line_no": 1, 

215 "start_line_pos": 1, 

216 "code": "L010", 

217 "description": "Palavras-chave devem estar em maiusculas", 

218 "name": "rule.name" 

219 } 

220 ] 

221 } 

222]""" 

223 result = parse_sqlfluff_output(output) 

224 assert_that(result).is_length(1) 

225 assert_that(result[0].message).contains("Palavras-chave") 

226 

227 

228def test_parse_sqlfluff_output_file_path_with_spaces() -> None: 

229 """Handle file paths with spaces.""" 

230 output = """[ 

231 { 

232 "filepath": "my project/sql files/query.sql", 

233 "violations": [ 

234 { 

235 "start_line_no": 1, 

236 "start_line_pos": 1, 

237 "code": "L010", 

238 "description": "Error", 

239 "name": "rule.name" 

240 } 

241 ] 

242 } 

243]""" 

244 result = parse_sqlfluff_output(output) 

245 assert_that(result).is_length(1) 

246 assert_that(result[0].file).contains("my project") 

247 

248 

249def test_parse_sqlfluff_output_deeply_nested_path() -> None: 

250 """Handle deeply nested file paths.""" 

251 deep_path = "a/b/c/d/e/f/g/h/i/j/query.sql" 

252 output = f"""[ 

253 {{ 

254 "filepath": "{deep_path}", 

255 "violations": [ 

256 {{ 

257 "start_line_no": 1, 

258 "start_line_pos": 1, 

259 "code": "L010", 

260 "description": "Error", 

261 "name": "rule.name" 

262 }} 

263 ] 

264 }} 

265]""" 

266 result = parse_sqlfluff_output(output) 

267 assert_that(result).is_length(1) 

268 assert_that(result[0].file).is_equal_to(deep_path) 

269 

270 

271def test_parse_sqlfluff_output_very_large_line_number() -> None: 

272 """Handle very large line numbers.""" 

273 output = """[ 

274 { 

275 "filepath": "query.sql", 

276 "violations": [ 

277 { 

278 "start_line_no": 999999, 

279 "start_line_pos": 1, 

280 "code": "L010", 

281 "description": "Error", 

282 "name": "rule.name" 

283 } 

284 ] 

285 } 

286]""" 

287 result = parse_sqlfluff_output(output) 

288 assert_that(result).is_length(1) 

289 assert_that(result[0].line).is_equal_to(999999) 

290 

291 

292def test_parse_sqlfluff_output_very_long_message() -> None: 

293 """Handle extremely long error messages.""" 

294 long_message = "x" * 5000 

295 output = f"""[ 

296 {{ 

297 "filepath": "query.sql", 

298 "violations": [ 

299 {{ 

300 "start_line_no": 1, 

301 "start_line_pos": 1, 

302 "code": "L010", 

303 "description": "{long_message}", 

304 "name": "rule.name" 

305 }} 

306 ] 

307 }} 

308]""" 

309 result = parse_sqlfluff_output(output) 

310 assert_that(result).is_length(1) 

311 assert_that(len(result[0].message)).is_equal_to(5000) 

312 

313 

314def test_parse_sqlfluff_output_special_chars_in_message() -> None: 

315 """Handle special characters in error messages.""" 

316 output = """[ 

317 { 

318 "filepath": "query.sql", 

319 "violations": [ 

320 { 

321 "start_line_no": 1, 

322 "start_line_pos": 1, 

323 "code": "L010", 

324 "description": "Use \\"quotes\\" and <brackets>", 

325 "name": "rule.name" 

326 } 

327 ] 

328 } 

329]""" 

330 result = parse_sqlfluff_output(output) 

331 assert_that(result).is_length(1) 

332 assert_that(result[0].message).contains("quotes") 

333 assert_that(result[0].message).contains("<brackets>") 

334 

335 

336def test_parse_sqlfluff_output_zero_line_number() -> None: 

337 """Handle zero line number (edge case).""" 

338 output = """[ 

339 { 

340 "filepath": "query.sql", 

341 "violations": [ 

342 { 

343 "start_line_no": 0, 

344 "start_line_pos": 1, 

345 "code": "L010", 

346 "description": "Error", 

347 "name": "rule.name" 

348 } 

349 ] 

350 } 

351]""" 

352 result = parse_sqlfluff_output(output) 

353 assert_that(result).is_length(1) 

354 assert_that(result[0].line).is_equal_to(0) 

355 

356 

357def test_parse_sqlfluff_output_null_violations() -> None: 

358 """Handle null violations array gracefully.""" 

359 output = """[ 

360 { 

361 "filepath": "query.sql", 

362 "violations": null 

363 } 

364]""" 

365 result = parse_sqlfluff_output(output) 

366 assert_that(result).is_empty() 

367 

368 

369def test_parse_sqlfluff_output_missing_violations_key() -> None: 

370 """Handle missing violations key gracefully.""" 

371 output = """[ 

372 { 

373 "filepath": "query.sql" 

374 } 

375]""" 

376 result = parse_sqlfluff_output(output) 

377 assert_that(result).is_empty() 

378 

379 

380def test_parse_sqlfluff_output_alternative_field_names() -> None: 

381 """Parse output with alternative field name mappings.""" 

382 output = """[ 

383 { 

384 "file": "query.sql", 

385 "violations": [ 

386 { 

387 "line": 5, 

388 "column": 10, 

389 "code": "L010", 

390 "message": "Alternative message format", 

391 "rule_name": "alternative.rule" 

392 } 

393 ] 

394 } 

395]""" 

396 result = parse_sqlfluff_output(output) 

397 assert_that(result).is_length(1) 

398 assert_that(result[0].file).is_equal_to("query.sql") 

399 assert_that(result[0].line).is_equal_to(5) 

400 assert_that(result[0].column).is_equal_to(10) 

401 assert_that(result[0].message).is_equal_to("Alternative message format") 

402 assert_that(result[0].rule_name).is_equal_to("alternative.rule")