Coverage for tests / unit / utils / test_parser_registry.py: 89%

120 statements  

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

1"""Unit tests for parser_registry module.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Generator 

6from typing import Any 

7 

8import pytest 

9from assertpy import assert_that 

10 

11from lintro.utils.output.parser_registration import ( 

12 ParserError, 

13 _parse_bandit_output, 

14) 

15from lintro.utils.output.parser_registry import ParserEntry, ParserRegistry 

16 

17 

18@pytest.fixture(autouse=True) 

19def reset_registry() -> Generator[None, None, None]: 

20 """Reset the parser registry before and after each test. 

21 

22 Yields: 

23 None: After clearing the registry and before restoring. 

24 """ 

25 # Store original parsers 

26 original_parsers = ParserRegistry._parsers.copy() 

27 ParserRegistry.clear() 

28 yield 

29 # Restore original parsers 

30 ParserRegistry._parsers = original_parsers 

31 

32 

33# ============================================================================= 

34# ParserEntry tests 

35# ============================================================================= 

36 

37 

38def test_parser_entry_creation() -> None: 

39 """Create a ParserEntry with parse function only.""" 

40 

41 def dummy_parser(output: str) -> list[Any]: 

42 return [] 

43 

44 entry = ParserEntry(parse_func=dummy_parser) 

45 assert_that(entry.parse_func).is_same_as(dummy_parser) 

46 assert_that(entry.is_fixable).is_none() 

47 

48 

49def test_parser_entry_with_fixability() -> None: 

50 """Create a ParserEntry with fixability predicate.""" 

51 

52 def dummy_parser(output: str) -> list[Any]: 

53 return [] 

54 

55 def is_fixable(issue: object) -> bool: 

56 return True 

57 

58 entry = ParserEntry(parse_func=dummy_parser, is_fixable=is_fixable) 

59 assert_that(entry.parse_func).is_same_as(dummy_parser) 

60 assert_that(entry.is_fixable).is_same_as(is_fixable) 

61 

62 

63# ============================================================================= 

64# ParserRegistry.register tests 

65# ============================================================================= 

66 

67 

68def test_register_parser() -> None: 

69 """Register a parser for a tool.""" 

70 

71 def my_parser(output: str) -> list[Any]: 

72 return [{"issue": "test"}] 

73 

74 ParserRegistry.register("mytool", my_parser) 

75 assert_that(ParserRegistry.is_registered("mytool")).is_true() 

76 

77 

78def test_register_parser_case_insensitive() -> None: 

79 """Tool names are stored in lowercase.""" 

80 

81 def my_parser(output: str) -> list[Any]: 

82 return [] 

83 

84 ParserRegistry.register("MyTool", my_parser) 

85 assert_that(ParserRegistry.is_registered("mytool")).is_true() 

86 assert_that(ParserRegistry.is_registered("MYTOOL")).is_true() 

87 assert_that(ParserRegistry.is_registered("MyTool")).is_true() 

88 

89 

90def test_register_parser_with_fixability() -> None: 

91 """Register a parser with fixability predicate.""" 

92 

93 def my_parser(output: str) -> list[Any]: 

94 return [] 

95 

96 def my_fixable(issue: object) -> bool: 

97 return hasattr(issue, "fixable") 

98 

99 ParserRegistry.register("mytool", my_parser, is_fixable=my_fixable) 

100 entry = ParserRegistry.get("mytool") 

101 assert_that(entry).is_not_none() 

102 assert_that(entry.is_fixable).is_same_as(my_fixable) # type: ignore[union-attr] 

103 

104 

105# ============================================================================= 

106# ParserRegistry.get tests 

107# ============================================================================= 

108 

109 

110def test_get_registered_parser() -> None: 

111 """Get a registered parser entry.""" 

112 

113 def my_parser(output: str) -> list[Any]: 

114 return [] 

115 

116 ParserRegistry.register("mytool", my_parser) 

117 entry = ParserRegistry.get("mytool") 

118 assert_that(entry).is_not_none() 

119 assert_that(entry.parse_func).is_same_as(my_parser) # type: ignore[union-attr] 

120 

121 

122def test_get_unregistered_parser_returns_none() -> None: 

123 """Get returns None for unregistered tools.""" 

124 entry = ParserRegistry.get("unknown_tool") 

125 assert_that(entry).is_none() 

126 

127 

128# ============================================================================= 

129# ParserRegistry.parse tests 

130# ============================================================================= 

131 

132 

133def test_parse_with_registered_parser() -> None: 

134 """Parse output using registered parser.""" 

135 

136 def my_parser(output: str) -> list[Any]: 

137 return [{"line": 1, "message": output}] 

138 

139 ParserRegistry.register("mytool", my_parser) 

140 result = ParserRegistry.parse("mytool", "test output") 

141 assert_that(result).is_length(1) 

142 assert_that(result[0]["message"]).is_equal_to("test output") 

143 

144 

145def test_parse_unknown_tool_returns_empty() -> None: 

146 """Parse returns empty list for unknown tools.""" 

147 result = ParserRegistry.parse("unknown_tool", "some output") 

148 assert_that(result).is_empty() 

149 

150 

151def test_parse_case_insensitive() -> None: 

152 """Parse works with any case of tool name.""" 

153 

154 def my_parser(output: str) -> list[Any]: 

155 return [{"found": True}] 

156 

157 ParserRegistry.register("MyTool", my_parser) 

158 result = ParserRegistry.parse("mytool", "test") 

159 assert_that(result).is_length(1) 

160 

161 

162# ============================================================================= 

163# ParserRegistry.get_fixability_predicate tests 

164# ============================================================================= 

165 

166 

167def test_get_fixability_predicate_registered() -> None: 

168 """Get fixability predicate for registered tool.""" 

169 

170 def my_parser(output: str) -> list[Any]: 

171 return [] 

172 

173 def my_fixable(issue: object) -> bool: 

174 return True 

175 

176 ParserRegistry.register("mytool", my_parser, is_fixable=my_fixable) 

177 predicate = ParserRegistry.get_fixability_predicate("mytool") 

178 assert_that(predicate).is_same_as(my_fixable) 

179 

180 

181def test_get_fixability_predicate_no_predicate() -> None: 

182 """Get returns None when tool has no fixability predicate.""" 

183 

184 def my_parser(output: str) -> list[Any]: 

185 return [] 

186 

187 ParserRegistry.register("mytool", my_parser) 

188 predicate = ParserRegistry.get_fixability_predicate("mytool") 

189 assert_that(predicate).is_none() 

190 

191 

192def test_get_fixability_predicate_unknown_tool() -> None: 

193 """Get returns None for unknown tools.""" 

194 predicate = ParserRegistry.get_fixability_predicate("unknown_tool") 

195 assert_that(predicate).is_none() 

196 

197 

198# ============================================================================= 

199# ParserRegistry.clear tests 

200# ============================================================================= 

201 

202 

203def test_clear_removes_all_parsers() -> None: 

204 """Clear removes all registered parsers.""" 

205 

206 def my_parser(output: str) -> list[Any]: 

207 return [] 

208 

209 ParserRegistry.register("tool1", my_parser) 

210 ParserRegistry.register("tool2", my_parser) 

211 assert_that(ParserRegistry.is_registered("tool1")).is_true() 

212 assert_that(ParserRegistry.is_registered("tool2")).is_true() 

213 

214 ParserRegistry.clear() 

215 assert_that(ParserRegistry.is_registered("tool1")).is_false() 

216 assert_that(ParserRegistry.is_registered("tool2")).is_false() 

217 

218 

219# ============================================================================= 

220# ParserRegistry.is_registered tests 

221# ============================================================================= 

222 

223 

224def test_is_registered_true() -> None: 

225 """Check if a tool is registered.""" 

226 

227 def my_parser(output: str) -> list[Any]: 

228 return [] 

229 

230 ParserRegistry.register("mytool", my_parser) 

231 assert_that(ParserRegistry.is_registered("mytool")).is_true() 

232 

233 

234def test_is_registered_false() -> None: 

235 """Check returns False for unregistered tools.""" 

236 assert_that(ParserRegistry.is_registered("unknown_tool")).is_false() 

237 

238 

239# ============================================================================= 

240# ParserError tests 

241# ============================================================================= 

242 

243 

244def test_parser_error_raised_on_parsing_failure() -> None: 

245 """Parser raises ParserError when parsing fails instead of returning empty list.""" 

246 with pytest.raises(ParserError) as exc_info: 

247 _parse_bandit_output("not valid json") 

248 

249 assert_that(str(exc_info.value)).contains("Failed to parse Bandit output") 

250 

251 

252def test_parser_error_raised_on_empty_output() -> None: 

253 """Parser raises ParserError for empty output.""" 

254 with pytest.raises(ParserError) as exc_info: 

255 _parse_bandit_output("") 

256 

257 assert_that(str(exc_info.value)).contains("Failed to parse Bandit output") 

258 

259 

260def test_parser_error_preserves_original_exception() -> None: 

261 """ParserError preserves the original exception as its cause.""" 

262 with pytest.raises(ParserError) as exc_info: 

263 _parse_bandit_output("{invalid json") 

264 

265 assert_that(exc_info.value.__cause__).is_not_none()