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

121 statements  

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

1"""Tests for lintro.parsers.base_issue module.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from typing import ClassVar 

7 

8import pytest 

9from assertpy import assert_that 

10 

11from lintro.enums.severity_level import SeverityLevel 

12from lintro.parsers.base_issue import BaseIssue 

13 

14 

15def test_base_issue_default_values() -> None: 

16 """BaseIssue has empty string and zero defaults.""" 

17 issue = BaseIssue() 

18 assert_that(issue.file).is_equal_to("") 

19 assert_that(issue.line).is_equal_to(0) 

20 assert_that(issue.column).is_equal_to(0) 

21 assert_that(issue.message).is_equal_to("") 

22 

23 

24def test_base_issue_accepts_values() -> None: 

25 """BaseIssue accepts custom values.""" 

26 issue = BaseIssue(file="test.py", line=10, column=5, message="Error found") 

27 assert_that(issue.file).is_equal_to("test.py") 

28 assert_that(issue.line).is_equal_to(10) 

29 assert_that(issue.column).is_equal_to(5) 

30 assert_that(issue.message).is_equal_to("Error found") 

31 

32 

33def test_to_display_row_basic_fields() -> None: 

34 """to_display_row includes basic fields.""" 

35 issue = BaseIssue(file="test.py", line=10, column=5, message="Test message") 

36 result = issue.to_display_row() 

37 assert_that(result["file"]).is_equal_to("test.py") 

38 assert_that(result["line"]).is_equal_to("10") 

39 assert_that(result["column"]).is_equal_to("5") 

40 assert_that(result["message"]).is_equal_to("Test message") 

41 

42 

43def test_to_display_row_zero_line_shows_dash() -> None: 

44 """to_display_row shows dash for zero line.""" 

45 issue = BaseIssue(file="test.py", line=0, column=0) 

46 result = issue.to_display_row() 

47 assert_that(result["line"]).is_equal_to("-") 

48 assert_that(result["column"]).is_equal_to("-") 

49 

50 

51def test_to_display_row_missing_optional_fields() -> None: 

52 """to_display_row handles missing optional fields.""" 

53 issue = BaseIssue() 

54 result = issue.to_display_row() 

55 assert_that(result["code"]).is_equal_to("") 

56 assert_that(result["severity"]).is_equal_to("WARNING") 

57 assert_that(result["fixable"]).is_equal_to("") 

58 

59 

60def test_display_field_map_class_variable() -> None: 

61 """BaseIssue has DISPLAY_FIELD_MAP class variable.""" 

62 assert_that(BaseIssue.DISPLAY_FIELD_MAP).contains_key("code") 

63 assert_that(BaseIssue.DISPLAY_FIELD_MAP).contains_key("severity") 

64 assert_that(BaseIssue.DISPLAY_FIELD_MAP).contains_key("fixable") 

65 assert_that(BaseIssue.DISPLAY_FIELD_MAP).contains_key("message") 

66 

67 

68def test_subclass_with_custom_fields() -> None: 

69 """Subclass can add custom fields.""" 

70 

71 @dataclass 

72 class CustomIssue(BaseIssue): 

73 code: str = "" 

74 severity: str = "" 

75 

76 issue = CustomIssue( 

77 file="test.py", 

78 line=1, 

79 column=1, 

80 message="Test", 

81 code="E001", 

82 severity="error", 

83 ) 

84 result = issue.to_display_row() 

85 assert_that(result["code"]).is_equal_to("E001") 

86 assert_that(result["severity"]).is_equal_to("ERROR") 

87 

88 

89def test_subclass_with_custom_field_map() -> None: 

90 """Subclass can customize field mapping.""" 

91 

92 @dataclass 

93 class MappedIssue(BaseIssue): 

94 DISPLAY_FIELD_MAP: ClassVar[dict[str, str]] = { 

95 "code": "rule_id", 

96 "severity": "level", 

97 "fixable": "fixable", 

98 "message": "message", 

99 } 

100 rule_id: str = "" 

101 level: str = "" 

102 

103 issue = MappedIssue( 

104 file="test.py", 

105 line=1, 

106 column=1, 

107 message="Test", 

108 rule_id="RULE001", 

109 level="warning", 

110 ) 

111 result = issue.to_display_row() 

112 assert_that(result["code"]).is_equal_to("RULE001") 

113 assert_that(result["severity"]).is_equal_to("WARNING") 

114 

115 

116def test_to_display_row_fixable_true() -> None: 

117 """to_display_row shows Yes for fixable=True.""" 

118 

119 @dataclass 

120 class FixableIssue(BaseIssue): 

121 fixable: bool = False 

122 

123 issue = FixableIssue(file="test.py", line=1, column=1, fixable=True) 

124 result = issue.to_display_row() 

125 assert_that(result["fixable"]).is_equal_to("Yes") 

126 

127 

128def test_to_display_row_fixable_false() -> None: 

129 """to_display_row shows empty string for fixable=False.""" 

130 

131 @dataclass 

132 class FixableIssue(BaseIssue): 

133 fixable: bool = False 

134 

135 issue = FixableIssue(file="test.py", line=1, column=1, fixable=False) 

136 result = issue.to_display_row() 

137 assert_that(result["fixable"]).is_equal_to("") 

138 

139 

140@pytest.mark.parametrize( 

141 ("line", "column", "expected_line", "expected_column"), 

142 [ 

143 (1, 1, "1", "1"), 

144 (100, 50, "100", "50"), 

145 (0, 5, "-", "5"), 

146 (10, 0, "10", "-"), 

147 ], 

148) 

149def test_to_display_row_line_column_formatting( 

150 line: int, 

151 column: int, 

152 expected_line: str, 

153 expected_column: str, 

154) -> None: 

155 """to_display_row formats line and column correctly. 

156 

157 Args: 

158 line: The line number to test. 

159 column: The column number to test. 

160 expected_line: The expected line string in display row. 

161 expected_column: The expected column string in display row. 

162 """ 

163 issue = BaseIssue(file="test.py", line=line, column=column) 

164 result = issue.to_display_row() 

165 assert_that(result["line"]).is_equal_to(expected_line) 

166 assert_that(result["column"]).is_equal_to(expected_column) 

167 

168 

169# ============================================================================= 

170# Tests for get_severity() 

171# ============================================================================= 

172 

173 

174def test_get_severity_returns_default_when_no_severity_field() -> None: 

175 """get_severity returns DEFAULT_SEVERITY when issue has no severity attr.""" 

176 issue = BaseIssue(file="test.py", line=1) 

177 assert_that(issue.get_severity()).is_equal_to(SeverityLevel.WARNING) 

178 

179 

180def test_get_severity_normalizes_string_field() -> None: 

181 """get_severity normalizes a native severity string.""" 

182 

183 @dataclass 

184 class SeverityIssue(BaseIssue): 

185 severity: str = "" 

186 

187 issue = SeverityIssue(file="test.py", line=1, severity="error") 

188 assert_that(issue.get_severity()).is_equal_to(SeverityLevel.ERROR) 

189 

190 

191def test_get_severity_uses_display_field_map() -> None: 

192 """get_severity resolves the attribute via DISPLAY_FIELD_MAP.""" 

193 

194 @dataclass 

195 class MappedIssue(BaseIssue): 

196 DISPLAY_FIELD_MAP: ClassVar[dict[str, str]] = { 

197 **BaseIssue.DISPLAY_FIELD_MAP, 

198 "severity": "level", 

199 } 

200 level: str = "" 

201 

202 issue = MappedIssue(file="test.py", line=1, level="warning") 

203 assert_that(issue.get_severity()).is_equal_to(SeverityLevel.WARNING) 

204 

205 

206def test_get_severity_falls_back_on_empty_string() -> None: 

207 """get_severity falls back to DEFAULT_SEVERITY for empty string.""" 

208 

209 @dataclass 

210 class SeverityIssue(BaseIssue): 

211 severity: str = "" 

212 

213 issue = SeverityIssue(file="test.py", line=1, severity="") 

214 assert_that(issue.get_severity()).is_equal_to(SeverityLevel.WARNING) 

215 

216 

217def test_get_severity_falls_back_on_unknown_value() -> None: 

218 """get_severity falls back to DEFAULT_SEVERITY for unrecognized strings.""" 

219 

220 @dataclass 

221 class SeverityIssue(BaseIssue): 

222 severity: str = "" 

223 

224 issue = SeverityIssue(file="test.py", line=1, severity="banana") 

225 assert_that(issue.get_severity()).is_equal_to(SeverityLevel.WARNING) 

226 

227 

228def test_get_severity_respects_custom_default() -> None: 

229 """get_severity uses subclass DEFAULT_SEVERITY.""" 

230 

231 @dataclass 

232 class InfoIssue(BaseIssue): 

233 DEFAULT_SEVERITY: ClassVar[SeverityLevel] = SeverityLevel.INFO 

234 

235 issue = InfoIssue(file="test.py", line=1) 

236 assert_that(issue.get_severity()).is_equal_to(SeverityLevel.INFO) 

237 

238 

239def test_get_severity_passes_through_enum_instance() -> None: 

240 """get_severity returns SeverityLevel instances unchanged.""" 

241 

242 @dataclass 

243 class EnumIssue(BaseIssue): 

244 severity: SeverityLevel = SeverityLevel.ERROR 

245 

246 issue = EnumIssue(file="test.py", line=1) 

247 assert_that(issue.get_severity()).is_equal_to(SeverityLevel.ERROR)