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
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unit tests for parser_registry module."""
3from __future__ import annotations
5from collections.abc import Generator
6from typing import Any
8import pytest
9from assertpy import assert_that
11from lintro.utils.output.parser_registration import (
12 ParserError,
13 _parse_bandit_output,
14)
15from lintro.utils.output.parser_registry import ParserEntry, ParserRegistry
18@pytest.fixture(autouse=True)
19def reset_registry() -> Generator[None, None, None]:
20 """Reset the parser registry before and after each test.
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
33# =============================================================================
34# ParserEntry tests
35# =============================================================================
38def test_parser_entry_creation() -> None:
39 """Create a ParserEntry with parse function only."""
41 def dummy_parser(output: str) -> list[Any]:
42 return []
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()
49def test_parser_entry_with_fixability() -> None:
50 """Create a ParserEntry with fixability predicate."""
52 def dummy_parser(output: str) -> list[Any]:
53 return []
55 def is_fixable(issue: object) -> bool:
56 return True
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)
63# =============================================================================
64# ParserRegistry.register tests
65# =============================================================================
68def test_register_parser() -> None:
69 """Register a parser for a tool."""
71 def my_parser(output: str) -> list[Any]:
72 return [{"issue": "test"}]
74 ParserRegistry.register("mytool", my_parser)
75 assert_that(ParserRegistry.is_registered("mytool")).is_true()
78def test_register_parser_case_insensitive() -> None:
79 """Tool names are stored in lowercase."""
81 def my_parser(output: str) -> list[Any]:
82 return []
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()
90def test_register_parser_with_fixability() -> None:
91 """Register a parser with fixability predicate."""
93 def my_parser(output: str) -> list[Any]:
94 return []
96 def my_fixable(issue: object) -> bool:
97 return hasattr(issue, "fixable")
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]
105# =============================================================================
106# ParserRegistry.get tests
107# =============================================================================
110def test_get_registered_parser() -> None:
111 """Get a registered parser entry."""
113 def my_parser(output: str) -> list[Any]:
114 return []
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]
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()
128# =============================================================================
129# ParserRegistry.parse tests
130# =============================================================================
133def test_parse_with_registered_parser() -> None:
134 """Parse output using registered parser."""
136 def my_parser(output: str) -> list[Any]:
137 return [{"line": 1, "message": output}]
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")
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()
151def test_parse_case_insensitive() -> None:
152 """Parse works with any case of tool name."""
154 def my_parser(output: str) -> list[Any]:
155 return [{"found": True}]
157 ParserRegistry.register("MyTool", my_parser)
158 result = ParserRegistry.parse("mytool", "test")
159 assert_that(result).is_length(1)
162# =============================================================================
163# ParserRegistry.get_fixability_predicate tests
164# =============================================================================
167def test_get_fixability_predicate_registered() -> None:
168 """Get fixability predicate for registered tool."""
170 def my_parser(output: str) -> list[Any]:
171 return []
173 def my_fixable(issue: object) -> bool:
174 return True
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)
181def test_get_fixability_predicate_no_predicate() -> None:
182 """Get returns None when tool has no fixability predicate."""
184 def my_parser(output: str) -> list[Any]:
185 return []
187 ParserRegistry.register("mytool", my_parser)
188 predicate = ParserRegistry.get_fixability_predicate("mytool")
189 assert_that(predicate).is_none()
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()
198# =============================================================================
199# ParserRegistry.clear tests
200# =============================================================================
203def test_clear_removes_all_parsers() -> None:
204 """Clear removes all registered parsers."""
206 def my_parser(output: str) -> list[Any]:
207 return []
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()
214 ParserRegistry.clear()
215 assert_that(ParserRegistry.is_registered("tool1")).is_false()
216 assert_that(ParserRegistry.is_registered("tool2")).is_false()
219# =============================================================================
220# ParserRegistry.is_registered tests
221# =============================================================================
224def test_is_registered_true() -> None:
225 """Check if a tool is registered."""
227 def my_parser(output: str) -> list[Any]:
228 return []
230 ParserRegistry.register("mytool", my_parser)
231 assert_that(ParserRegistry.is_registered("mytool")).is_true()
234def test_is_registered_false() -> None:
235 """Check returns False for unregistered tools."""
236 assert_that(ParserRegistry.is_registered("unknown_tool")).is_false()
239# =============================================================================
240# ParserError tests
241# =============================================================================
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")
249 assert_that(str(exc_info.value)).contains("Failed to parse Bandit output")
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("")
257 assert_that(str(exc_info.value)).contains("Failed to parse Bandit output")
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")
265 assert_that(exc_info.value.__cause__).is_not_none()